In projects built with webpack, it can be convenient to dynamically create some files in memory. In other words, a file doesn’t need to be written into the file system. Still, webpack must treat this virtual file as a real module and rebuild the project on the fly whenever we change it. This functionality is especially useful when you need to automatically generate documentation for a RESTful API.
To be able to generate and inject virtual modules into the builds, we created a dedicated plugin Webpack Virtual Modules.
Have a look at the code below. Notice how in the second line swagger.json
is imported. The fact is, this file does not exist in the file system — it’s virtual and is created by Webpack Virtual Module at compile time. You can look up the example in the plugin repository to see for yourself that there’s no swagger.json
.
// Require a virtual module generated by Webpack Virtual Modules in memory
const swaggerJson = require('swagger.json');
const swaggerUi = require('swagger-ui');
require('swagger-ui/dist/swagger-ui.css');
/**
* @swagger
* /api/hello:
* get:
* description: Returns hello message
* parameters:
* - name: subject
* in: query
* schema:
* type: string
* responses:
* '200':
* content:
* application/json:
* schema:
* type: string
*/
function getHello(name) {
// TODO: Replace the code with a REST API call when it's implemented on the backend
return { message: 'Hello ' + name + '!' };
}
var helloDiv = document.getElementById('hello');
helloDiv.innerHTML = getHello('World').message;
swaggerUi({
spec: swaggerJson, dom_id: '#apiDocs',
presets: [
swaggerUi.presets.apis,
swaggerUi.SwaggerUIStandalonePreset
]
});
Code language: JavaScript (javascript)
If you run the example and change the documentation (see the comment /**... @swagger...*/
), you’ll notice that webpack will recompile the project as if the swagger.json
file really existed.
How does Webpack Virtual Modules actually work? Let’s have a closer look at the example in the plugin repository.
Generating virtual files with Webpack Virtual Modules
We want to talk through a few important aspects how to use Webpack Virtual Modules.
To generate a virtual file with this plugin, you first need to create a path and a string with the contents for the file. The initial contents may be empty.
Second, you need to instantiate Webpack Virtual Modules. The VirtualModulesPlugin
class optionally accepts an object of string key-value pairs where keys are paths to the virtual modules and values are the contents.
Next, you need to pass the created plugin instance to the plugins
array in webpack configuration. Finally, you need to use webpack compiler hooks to track the plugin instance, for example, compiler.hooks.compilation.tap(ModuleName, (compilation) => {}
. Inside the callback passed to the hook, you can write to the virtual module using the method VirtualModulesPlugin.writeModule()
. And you can update the module in any project file using the same method.
writeModule()
, as you might have guessed, accepts an object parameter: The key will again be the path to the virtual file, and a string with contents will be the value.
Below, you can see an example that follows the flow we described. This is a Swagger plugin we created to demo the Webpack Virtual Modules plugin (to recall how webpack plugins are created, check out the Writing a Plugin guide).
Here’s the code.
const VirtualModulesPlugin = require('../..');
const swaggerJsDoc = require('swagger-jsdoc');
function SwaggerPlugin() {}
SwaggerPlugin.prototype.apply = function(compiler) {
// #1
// Create a package.json module, path, and file
const pkgJsonModule = './package.json';
const pkgJsonPath = require.resolve(pkgJsonModule);
const pkgJson = require(pkgJsonModule);
// #2
// Sample data for the future virtual JSON file
const info = {
title: pkgJson.name,
version: pkgJson.version,
description: pkgJson.description
};
// #3
// Using the 'package.json' path to create the path to
// the virtual module `swagger.json`.
// Webpack will "see" the module `swagger.json`
// by a path similar to this:
// '/home/johndoe/webpack-virtual-modules/examples/node_modules/swagger.json'
const swaggerJsonPath = path.join(
path.dirname(pkgJsonPath),
'node_modules',
'swagger.json');
// #4
// Create a new virtual module with the initial content
const virtualModules = new VirtualModulesPlugin({
[swaggerJsonPath]: JSON.stringify({
openapi: '3.0.0',
info: info
})
});
// #5
// Set up webpack hooks to listen to `SwaggerPlugin` event
virtualModules.apply(compiler);
compiler.hooks.compilation.tap('SwaggerPlugin', function(compilation) {
try {
// Using swagger-jsdoc to generate a new virtual JSON file
const swaggerJson = swaggerJsDoc({
swaggerDefinition: {
openapi: '3.0.0',
info: info
},
apis: ['*.js', '!(node_modules)/**/*.js']
});
// Writing a new virtual module will happen each time the project is changed
virtualModules.writeModule(swaggerJsonPath, JSON.stringify(swaggerJson));
} catch (e) {
compilation.errors.push(e);
}
});
}
Code language: JavaScript (javascript)
Here’s what happens in this file:
- The
package.json
module, path, and file are created - We create sample data for the future virtual module
swagger.json
- Using the path to
package.json
, we create a path to the virtual module - We instantiate
VirtualModulesPlugin()
with an object for the new virtual module. The object must contain a path to the virtual file and stringified data to be written into the file virtualModules
runsapply()
to add webpack hooks to listen for theSwaggerPlugin
event- Inside the hook, the
swagger.json
file is generated
Our example is a bit more contrived than what we explained in the beginning of this section. For one, we created a webpack plugin to generate Swagger documentation. Notice how inside the plugin we invoke virtualModules.apply(compiler)
. This sets a few webpack hooks on the compiler to enable dynamic creation of webpack modules. Put simply, running virtualModules.apply(compiler)
enables the compiler to start tracking the virtual module SwaggerPlugin
.
Let’s now get back to Swagger plugin. To make it work, we need to pass instantiated SwaggerPlugin
into the plugins
in webpack configurations:
// webpack configurations
module.exports = {
entry: './index.js',
plugins: [new SwaggerPlugin()], // Instantiate the plugin
// ...
};
Code language: JavaScript (javascript)
That’s all. You can run the example, add your own comment with Swagger documentation or change the existing one, and view the updated documentation in your browser.
It’s simple to use Webpack Virtual Modules plugin. Check out the plugin repository if you’d like to learn more about it.