In this paper, the content

  • How to develop webpack plug-in and develop small package plug-in process
  • Problems encountered during the development process and how to solve them
  • What can mini-program-Webpack-loader do

Develop the WebPack plug-in

For those of you who have developed plug-ins, you may have read the article Writing A Plugin or something similar. The mini-program-Webpack-loader tool was developed when WebPack 4 was released, so I read this article and read the following documents.

  • Writing a Plugin
  • Plugin Patterns
  • Plugin API
  • Loader API

If you’ve seen the documentation, you know this:

  • Each plug-in must have apply methods for the WebPack engine to execute the code you want.
  • Compiler hooks are the two important object Compiler and Compilation that you can bind to event hooks (which are called when Webpack executes to this step).
  • The relationship between module and chunk can be understood as that each file has a module, while a chunk is composed of multiple modules.
  • Webpack has those events for the entire packaging process
  • How to write a simple loader

If you don’t know what to do, follow up on how I developed and improved the Mini-Program-Webpack-loader step by step to package small programs.

Json file to define all the page paths, and then each page consists of four files:.js,.json,.wxml, and.wxss. Therefore, I take app.json as webpack entry. When Webpack performs the apply of the plug-in, it can obtain the entry to know what pages the small program has. The process is like the following figure, a small program package plug-in is almost complete.

Two plug-ins, MultiEntryPlugin and SingleEntryPlugin, are used here. Why do you do that? Because Webpack determines the number of files to generate based on your entry configuration (not just webpack configuration, import(), require.ensure() all generate an entry), We don’t want to pack all the js pages into a single file, we need to use the SingleEntryPlugin to generate a new entry module; For those static resources, we can use the MultiEntryPlugin to deal with these files as an entry module dependency, configure file-loader in the loader can output static files. The pseudocode is as follows:

 const MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin');
 const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
 
 class MiniPlugin {
    apply (compiler) {
        let options = compiler.options
        let context = compiler.rootContext
        let entry = options.entry
        let files = loadFiles(entry)
        let scripts = files.filter(file => /\.js$/.test(file))
        letassets = files.filter(file => ! /\.js$/.test(file)) new MultiEntryPlugin(context, assets,'__assets__').apply(compiler)
        
        scripts.forEach((file => {
            let fileName = relative(context, file).replace(extname(file), ' '); new SingleEntryPlugin(context, file, fileName).apply(compiler); }}})Copy the code

Of course, if you do this, you’ll end up with a main.js, xxx.js (the name of the MultiEntryPlugin), which corresponds to the file generated by the configured entry, Xxx. js is generated by MultiEntryPlugin. These files are not what we need, so we need to get rid of them. If you are familiar with Webpack documentation, there are many places where you can modify the resulting packaged files, such as emit events in the Compiler, or events related to the Compilation optimizeChunks. It’s essentially modifying the compiler.assets object.

Emit events are used in the Mini-Program-Webpack-loader to process content that does not require output. The process is like this:

WXML, WXSS, WXS, and custom component references are also supported. Therefore, a Loader is needed to complete this task. The loader needs to parse the dependent files. For example,.wxml needs to parse the SRC of the import component, WXS SRC,.wxss needs to parse @import, WXS require, and finally loadModule can be added in the loader. Custom components are initially retrieved directly in the Add Entry step, so there is no need for loader to complete them. The picture at this time:

There is no problem with this, but the development experience is relatively poor. For example, if you add a custom component, a page, Webpack is not aware, so you need to check whether a custom component is added or a new page is added when the.json in the page changes. The problem with this is that the js of a custom component cannot be added by addModule, because the js of a custom component must be a separate entry file. It cannot be done in loader, so try to upload the file to plugin (since plugin executes before Loader, it is possible to establish communication between Loader and plugin). The simple and crude way:

// loader.js
class MiniLoader {}

module.exports = function (content) {
    new MiniLoader(this, content)
}
module.exports.$applyPluginInstance = function (plugin) {
  MiniLoader.prototype.$plugin = plugin
}

// plugin.js
const loader = require('./loader')
class MiniPlugin {
    apply (compiler) {
        loader.$applyPluginInstance(this); }}Copy the code

But… . The file is uploaded to the plugin, but when you use the SingleEntryPlugin you will find that it has no effect. After Compiler Make, Webpack can no longer sense the addition of new modules, so it is useless. At this time, we need to guess according to the document how to let Webpack sense the new module, and make keyword query according to the events in the document. You can see that the Compilation needAdditionalPass event hook is called when the compilation is complete:

    this.emitAssets(compilation, err => {
    	if (err) return finalCallback(err);
    
    	if (compilation.hooks.needAdditionalPass.call()) {
    		compilation.needAdditionalPass = true;
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    
    			this.hooks.additionalPass.callAsync(err= > {
    				if (err) return finalCallback(err);
    				this.compile(onCompiled);
    			});
    		});
    		return;
    	}
    
    	this.emitRecords(err= > {
    		if (err) return finalCallback(err);
    
    		const stats = new Stats(compilation);
    		stats.startTime = startTime;
    		stats.endTime = Date.now();
    		this.hooks.done.callAsync(stats, err => {
    			if (err) return finalCallback(err);
    			return finalCallback(null, stats);
    		});
    	});
    });
Copy the code

If the event hook returns a true value, you can make the Webpack call compiler additionalPass to try to add the file here, which it does. Now the picture looks like this:

Of course, there are a few different aspects of applet packaging, such as subcontracting, and how to use SplitChunk is not a long story, but once you get started there are many ways to achieve the desired effect.

So far in plug-in development, webPack is basically a variation of callbacks, and it’s easy to use when you know what each callback is supposed to do. Obviously I don’t know, because there were some problems during development.

Problems encountered

1. How to support resolve alias, node_modules in applets?

Resolve Alias, node_modules, resolve alias, node_modules, resolve Alias, node_modules, resolve Alias, node_modules, resolve Alias Of course we want to be able to use aliases in any file, node_modules supports more than just JS. Of course, this means things get complicated, starting with getting the file path, which must be asynchronous, since Resolve no longer supports sync in WebPack 4. The second is that the directory name of the applet cannot be node_modules, so there needs to be a rule to calculate the relative path, and still package the output relative to the current project directory.

2. Merging of multiple applets

Praise small procedures, there are micro mall version, retail version, as well as the public version, most of which basic functions, business is the same, of course, no longer each small procedures in the development of a time, so this tool has to merge multiple small procedures of course is necessary. This merging is slightly more complicated than fetching files from node_modules, because you need to make sure that the combined pages of multiple applets are correct and that the path remains the same.

The final solution to these two problems is to take the SRC directory of webPack rootContext as the base directory, calculate the absolute path of the packaged file based on the path of the SRC directory, and then calculate the final output path based on the path of the app.json directory of the entry file.


exports.getDistPath = (compilerContext, entryContexts) = > {
  /** * webpack uses the SRC directory of config as the package entry * so you can trace the source file address */
  return (path) = > {
    let fullPath = compilerContext
    let npmReg = /node_modules/g
    let pDirReg = /^[_|\.\.]\//g

    if (isAbsolute(path)) {
      fullPath = path
    } else {
      // Relative path: the last path generated by Webpack. All files outside the package entry are denoted by '_'

      while (pDirReg.test(path)) {
        path = path.substr(pDirReg.lastIndex)
        fullPath = join(fullPath, '.. / ')}if(fullPath ! == compilerContext) { fullPath = join(fullPath, path) } }// Obtain the packaged directory according to the JSON file directory defined in Entry. If the directory cannot be obtained, return to the original path
    let contextReg = new RegExp(entryContexts.join('|'), 'g')

    if(fullPath ! == compilerContext && contextReg.exec(fullPath)) { path = fullPath.substr(contextReg.lastIndex +1)
      console.assert(! npmReg.test(path),` file${path}Path error: node_modules' should not also be included)}If app.json is in node_modules, then the path should not contain node_modules */

    if (npmReg.test(path)) {
      path = path.substr(npmReg.lastIndex + 1)}return path
  }
}
Copy the code

3. How to package the content that a subpackage depends on separately into a subpackage

The solution to this problem is to add the chunk entry file to the dependent module of each chunk through the optimizeChunks event, and then check the number of dependencies of the Module in splitChunk’s test configuration. If there is only one, and it is a bundle dependent, it is packaged into a subpackage.

4. Webpack supports single file failure

This is an unsolved problem that doesn’t seem so convenient when trying to use Webpack to support single files:

  • After splitting a single file into four files, emitFile and addDependency can be used to create files, but the created files do not execute loader
  • Using loadModule will cause an error because the file does not exist on the file system

Write in the last

Finally, of course, it introduces what mini-program-Webpack-loader can do.

The tool mainly solves the following problems:

  • Applets do not support NPM
  • Directories are too deeply nested to manage paths
  • Old projects are too big and new tools are too expensive to use
  • Provide general optimization recommendations for large applets projects

repeat

  • You can download ZANui-downloadP directly using NPM
  • You can say goodbye to “.. /.. /.. /.. /.. / XXX”
  • You can use mpVue and Taro to write new features without worrying about incompatibility

The last remaining document address: Mini-program-webpack-loader