Webpack’s problems with “multi-page” development

We often use the webpack-dev-server plugin provided by Webpack. Using webpack-dev-server, we can provide Hot updates “Live Reload” and Hot Module Replacement (HMR) via a single entry main.js and the entry page HTML, which is very convenient. But in real projects we encountered more complex scenarios.

For example, we now have a project that is not just a SPA application, it may be a project made up of several SPA applications, each of which may be maintained by a different person. So it will have multiple entry JS and entry HTML. Without providing a unique entry point to JS and HTML, a webpack-dev-server solution would not work. For this kind of “multi-page application” project, the best solution is to be able to switch to the corresponding SPA application, i.e. the corresponding entry JS and HTML, during development, and we need a few tricks to do this.

The solution

Before using Webpack, we didn’t have this kind of trouble. Maybe this is the only way to “spiral”. In short, there were three initial scenarios that I could think of.

  1. In development mode, listen for file changes through Gulp, then package files directly using Webpack, and use gulp-Server to handle routing.
  2. Still use webpack-dev-server and proxy another server to handle routing.
  3. Just one Server + WebpackMiddleware adds multiple routes while keeping hot updates and hot replacements.

Each of the three methods has advantages and disadvantages. In this case, we choose the third option, which allows us to handle Mock Post requests and Prox issues more easily with a separate Server.



In the picturemultientry-dev-serverThat’s the server we’re going to create.

Webpack Dev\Hot Middleware

Webpack-dev-server is an Express server. Webpack-dev-middleware and Webpack-hot-middleware are used as middleware to provide hot Module Replacement/ hot Reloading capabilities. Webpack Hot Middleware must be used with Webpack Dev Middleware, so in the same way that Express can be used to start a server with Hot replacement, Webpack-dev-server is simply a wrapper.

var http = require('http');
var express = require('express');
var path = require('path');
var webpackMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');Copy the code

Create an instance of Webpack and use middleware with the following code

var app = express();
var compiler = webpack(webpackConfig);
var middleware = webpackMiddleware(compiler, {
  publicPath: webpackConfig.output.publicPath
});
app.use(middleware);
app.use(webpackHotMiddleware(compiler));
Copy the code

Get the Webapck package file in memory by routing

Packaging with webPack middleware does not actually generate files, it loads them into memory.

In order to specify the route to the corresponding entry JS and HTML, we need to make some conventions in the project. Assume that the project entry isappsDirectory. Each subdirectory in this directory corresponds to a SPA applicationpackage.jsonSpecify the entry JS for the SPA app and other information about the SPA app, such as the name and child app owner.



The package.json file format is similar to the following:

{" name ":" app1 ", "main" : ". / main. Js ", "author" : "ZuoLun}"Copy the code

Middleware provides middleware. FileSystem. ReadFileSync method reads an in-memory file, the file of address is configured in webpack output.

app.get('/:appName', function (req, res) {
  var result = '';
  var htmlPath = path.join(__dirname, webpackConfig.output.path + req.params.appName + '/index.html');
  console.log(htmlPath);
  try {
    result = middleware.fileSystem
      .readFileSync(htmlPath);
  } catch (err) {
    result = err.toString();
  }
  res.write(result);
  res.end();
});
Copy the code

OK, so you can specify the entry to the corresponding App through the route.

Mock data and the “home page”

Since the Server is started independently, we can easily specify any directory in the Server as our static directory and handle the corresponding Post requests.

Use (express.static(path.join(__dirname, '.. / '))); // Mock POST request app.post('/ API /*', function (req, res) {res.sendFile(path.join(__dirname, '.. /api', req.params[0])); });Copy the code

As more and more subapps are in the project, it becomes cumbersome to manually enter routes into the browser and then jump to them. Therefore, you can add a “home page” in the Server to list all the sub-applications under the current project and the corresponding route during development. It is not difficult to implement this, by traversing the packge. json in each application directory, the result is as follows:



Finally, I don’t have to type in my browser every time…

So far, we’ve successfully addressed the development issues of Webpack in “multi-page” applications, so it’s time to take it one step further.

usetargetSpecify entry application

As mentioned earlier, the Webpack middleware will read files into memory when it is built, but as our project gets bigger and bigger, there will be more and more sub-applications under the project, which causes another problem. Sometimes we’re just developing code for a subapp, but Webpack packs the entire project into memory every time, which is a waste of resources. So here we can do a little bit of work in the webpack configuration file and figure out how to package only the JS in the directory we need.

When executing NPM start, use the

target=appName1,appName2 npm startCopy the code

Where appName is the name of the application you want to launch (the name is defined in package.json), the Entry in the Webpack configuration will only contain the Entry JS and HTML of the target specified application name, greatly shortening the Webpack startup time and reducing the memory footprint. The idea was first seen in the code of another senior member of the team. It was very clever, and the key code was as follows:

var targetEntries = process.env.target; targetEntries = targetEntries ? targetEntries.split(',') : ''; TargetEntries. ForEach (function (value) {console.log(' apply: ', value); entryPath = path.join(viewsDir, value); entryJson = fse.readJsonSync(path.join(entryPath, '/package.json')); entryMap[value] = [path.resolve(path.join(entryPath, entryJson.main))]; var appName = entryJson.name; var tplPath = path.join(entryPath, '/index.html'); var conf = { template: tplPath, filename: path.join(appName, 'index.html'), inject: 'body', chunks: [appName] }; htmlPluginsArr.push(new HtmlWebpackPlugin(conf)); }); _.extend(webpackConfig, { entry: entryMap, output: { path: path.join(__dirname, '.. /build/'), filename: '[name]/[name].min.js' }, plugins: [ new ExtractTextPlugin('[name]/[name].css'), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, drop_console: true } }) ].concat(htmlPluginsArr) });Copy the code

With such a simple service, we don’t need to use Webpack-dev-server at all, and we can even encapsulate a similar plugin. This is the end of the whole problem of ideas and solutions, if you have better ideas can be added.

This article was first published on Alisec-Ued, personal blog address.