I was wandering around the front-end forums recently and saw some articles about Parcel and Webpack, and I was curious about the packaging tools I use every day and how they work. Only when you know this, can you find the most suitable one among many packing tools. Before we look at how packaging works, let’s spend a few chapters explaining why you should use a packaging tool.

0. Module system

The delivery of front-end products is based on the browser, and these resources are run to the browser side through incremental loading. How to organize these fragmented code and resources in the development environment, and ensure that they can be loaded and updated quickly and elegantly in the browser side, requires a modular system. This ideal modular system is a problem that front-end engineers have been exploring for years.

Module system mainly deals with module definition, dependency and export. The original

Therefore, many modular solutions have been derived:

1.CommonJs: Advantages: Server-side modules are easy to reuse. Disadvantages: Synchronous module loading is not suitable in the browser environment, synchronous means blocking loading, browser resources are loaded asynchronously.

AMD: Rely on the front end. Advantages: Suitable for asynchronous loading in the browser environment; Disadvantages: Difficult to read and write.

3.CMD: depends on the nearest, delay execution. Advantages: Easy to run in Node; Disadvantages: dependent on SPM packaging, heavy loading logic of modules.

4.ES6 module: : Static as far as possible, so that the dependency of the module and the input and output variables can be determined at compile time. Both CommonJS and AMD modules can only determine these things at runtime. Advantages: easy static analysis; Disadvantages: Native browsers do not implement this standard.

When it comes to module loading and transfer, if each file is requested individually, it will result in too many requests and slow startup. If all requests are packaged and sent only once, traffic is wasted and the initialization process is slow. Therefore, the best solution is to block transfer, lazily load on demand, and then incrementally update some modules when they are actually used. To load modules on demand, a process of static analysis, compilation and packaging of modules in the entire code base is required. Webpack was born out of this need.

Note: Note the concept of modules for everything. Styles, images, fonts, HTML templates, and many other resources can all be considered modules.

1. Module packer: Webpack

Webpack is a module packer. It performs static analysis based on module dependencies, and then generates the corresponding static resources from these modules according to the specified rules. So the question is, can WebPack really do all the static analysis, compilation and packaging mentioned above? Let’s first look at what WebPack can do:

Webpack has two ways of organizing module dependencies, synchronous and asynchronous. Asynchronous dependencies act as split points, forming a new block. After optimizing the dependency tree, each asynchronous block is packaged as a file.

2.Loader Webpack itself can only handle native JavaScript modules, but Loader converters can convert various types of resources into JavaScript modules. In this way, any resource can become a module that Webpack can handle.

Webpack has a smart parser that can handle almost any third party library, whether they come in the form of modules such as CommonJS, AMD, or plain OLD JS files.

Webpack also has a feature-rich plug-in system. Most of the content functions run on this plug-in system, and open source Webpack plug-ins can be developed and used to meet a variety of needs.

5. Fast Running Webpack Improves runtime efficiency with asynchronous I/O and multi-level caching, which enables Webpack to compile incrementally at an incredibly fast rate.

The above are the five main features of Webpack, but after reading it, I still feel a little confused. How does Webpack integrate some scattered small modules into large modules? How to deal with the dependency of each module? Take Parcel core developer @Ronami’s open source project MiniPack as an example.

2. Core principles of packaging tools – Take MiniPack as an example

Packaging tool is responsible for some scattered small modules, according to certain rules into a large module tool. At the same time, the packaging tool handles dependencies between modules and runs the project on the platform. One of the most important questions that the MiniPack project illustrates, and is at the heart of the packaging tool, is how to manage dependencies between modules.

First, the packaging tool will start with an entry file, analyze the dependencies within it, and further analyze the dependencies within the dependencies. We create three new files and create dependencies:

/* name.js */
export const name = 'World'

/* message.js */
import { name } from './name.js'
export default `Hello ${name}! ` /* entry.js */ import message from'./message.js'
console.log(message)
Copy the code

Start by introducing the necessary tools

/* minipack.js */
const fs = require('fs')
const path = require('path')
const babylon = require('babylon')
const traverse = require('babel-traverse').default
const { transformFromAst } = require('babel-core')
Copy the code

Next we will create a function that takes the path to the file and reads the contents of the file and extracts its dependencies.

functionCreateAsset (filename) {// Read the file as a string const content = fs.readfilesync (filename,'utf-8'); // Now we are trying to find out which file this file depends on. Although we can retrieve the import string by looking at its contents. However, this is a very cumbersome method and we'll use a JavaScript parser instead. // JavaScript parsers are tools that can read and understand JavaScript code. They generate a more abstract model called 'AST (Abstract Syntax Tree)(https://astexplorer.net)'. const ast = babylon.parse(content, {sourceType: 'module'}); Const dependencies = []; const dependencies = []; // We walk through the ast to try to understand which modules the module depends on. To do this, we need to check each 'import' declaration in the AST. // 'Ecmascript' modules are quite simple because they are static. This means that you cannot 'import' a variable, or conditionally 'import' another module. Every time we see an 'import' declaration, we can treat its value as a 'dependency'. Traverse (ast, {ImportDeclaration: ({node}) => // Traverse (ast, {node}) => }}); // We also assign a unique identifier to this module by incrementing the simple counter const id = ID++; // We use the 'Ecmascript' module and other JavaScript and may not support all browsers. // To ensure that our program runs in all browsers, // we will use [Babel](https://babeljs.io) for the conversion. Const {code} = transformFromAst(ast, null, {presets: [) const {code} = transformFromAst(ast, null, {presets: ['env']}); // Returns all information about this module.return {
    id,
    filename,
    dependencies,
    code,
  };
}

Copy the code

Now that we can extract the dependencies of a single module, we will extract the dependencies of each of its dependencies and loop until we understand each module in the application and how they depend on each other.

functionCreateGraph (entry) {// Parse the entire file first, const mainAsset = createAsset(entry); Const queue = [mainAsset]; const queue = [mainAsset]; const queue = [mainAsset]; // We use a 'for. 'of' loops through the queue. // Initially the queue had only one asset, but when we iterate over it, we push extra assert to the queue. // The loop terminates when the queue is empty.for(const asset of queue) {// Each of our assets has a list of relative paths to the modules it depends on. // We'll repeat them, parse them with our 'createAsset()' function, and track the module's dependencies in this object. asset.mapping = {}; Const dirname = path.dirname(asset.filename); // This is the directory where the module resides. / / we traverse the list of related path in the asset. The dependencies. The forEach (relativePath = > {/ / we can by the parent with the relative path resource directory path connection, the relative path into an absolute path. Const absolutePath = path.join(dirname, relativePath); Const child = createAsset(absolutePath); // It is important for us to understand that the 'asset' dependency depends on 'child'. // This one-to-one correspondence is expressed by adding a new attribute to the 'asset-.mapping' object with the value child.id. asset.mapping[relativePath] = child.id; Queue.push (child); queue.push(child); // Finally, we queue the asset 'child' so that its dependencies are also iterated and resolved. }); }return queue;
}
Copy the code

Next we define a function that passes in the graph from the previous step and returns a package that can be run on the browser.

function bundle(graph) {
  let modules = ' '; // Before we reach the body of the function, we will build an object as an argument to the function. // Note that the string we are building is wrapped in two curly braces ({}), so for each module, we add a string of this form: 'key:'. Value, '.graph.foreach (mod => {// Each module in the graph has an entry in this object. We use the module id as' key 'and the array as' value' // the first argument is the code for each module wrapped in functions. This is because modules should be scoped: defining variables in one module does not affect other modules or the global scope. The parsed object looks like this: '{'./relative/path': 1} '. // This is because our module's converted path calls' require() '. When this function is called, we should be able to know which module in the dependency graph corresponds to the relative path of that module. Modules += '${mod.id}: [
      function (require, module, exports) { ${mod.code} },
      ${JSON.stringify(mod.mapping)},], `; // Last, using 'commonjs', it can expose the module's value by changing the exports object when it needs to be exported. // The require function returns the exports object.function(modules) {
      function require(id) { 
        const [fn, mapping] = modules[id];
        function localRequire(name) { 
          return require(mapping[name]); 
        }
        const module = { exports : {} };
        fn(localRequire, module, module.exports); 
        returnmodule.exports; } require(0); ({})${modules}`});return result;
  });
Copy the code

Run!

const graph = createGraph('./example/entry.js'); const result = bundle(graph); // Get the result, happy! console.log(result);Copy the code

More information is available at the project’s Github address

3. Summary

Webpack solves the potential problem of cyclic dependencies between packages, while merging static files as needed to avoid concurrency bottlenecks in the browser’s network fetch phase. In addition to packaging, compression (reduced network traffic) and compilation (ES6, JSX, and other syntax-backward compatibility) can be further implemented.

Based on the configuration of the webpack.config.js file, the working principle of packaging can be summarized as follows: the page logic is treated as a whole, through a given entry file, Webpack starts from this file, finds all the dependent files, packages, compacts, compacts, and finally outputs a browser recognized JS file.

A module packaging tool, the first step will start from the entry file, its dependency analysis, the second step of all its dependencies recursion dependency analysis, the third step to build the dependency map of the module, the last step according to the dependency map using CommonJS specification to build the final code.

4. Refer to web sites

https://mp.weixin.qq.com/s/w-oXmHNSyu0Y_IlfmDwJKQ

https://github.com/chinanf-boy/minipack-explain/blob/master/src/minipack.js

https://zhaoda.net/webpack-handbook/configuration.html