webpack

To analyze the packaging principle of Webpack, we need to know the core parts of Webpack:

  1. Entry: Defines the starting point of the entire compilation process
  2. Output: Defines the end of the compilation process
  3. Module: Defines how the module module is handled
  4. Pulgin takes a second look at the compiled content
  5. Resolve. Alias Defines the alias of the module

This is part of a standard WebPack configuration file.

modules.export = {
    entry: './index.js',
    output: {
        path: path.resolve(process.cwd(),'dist/'),
        filename: '[name].js'
    },
    resolve:{
        alias:{query:'src/lib/query.js'}
    },
    module: {
        rules: [
          { test: /\.txt$/, use: 'raw-loader' }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({template: './src/index.html'})
    ]
}

module.exports = config;
Copy the code

webpackdemo

Take a simple example of what the WebPack packaging process does from a WebPack application:

  1. Create the webPackdemo directory and run itnpm init -yInitialize the project
  2. Install dependencies in WebPackDemo

npm install webpack webpack-cli -D 3. Create SRC /index.js and SRC /a.js

// a.js
const sayAge = require('./common/util.js')
module.exports=(name)=>{
    console.log('hello world'+name)
    sayAge(18)
}
Copy the code
// index.js
let sayHi = require('./a.js')
sayHi('webpack')
Copy the code

New webpack.config.js, a simple WebPack configuration only needs to define the entry and exit:

module.export = {
    mode: 'development',
    entry: './src/index.js',
    output: {
        filename: 'pack.js'
    }
}
Copy the code

Webpack compiled code

Execute NPX webpack to see the packed code and remove some useless code. It looks something like this

(function(modules) { // webpackBootstrap // The module cache var installedModules = {}; // The require function function __webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Return the exports of the module return module.exports; } // Load entry module and return exports return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) ({ "./src/a.js":(function(module, exports) { eval("module.exports=(name)=>{\n console.log('hello world'+name)\n}\n\n//# sourceURL=webpack:///./src/a.js?" ); }), "./src/index.js": (function(module, exports, __webpack_require__) { eval("let sayHi = __webpack_require__(/*! ./a.js */ \"./src/a.js\")\nsayHi('webpack')\n\n//# sourceURL=webpack:///./src/index.js?" ); })});Copy the code

The WebPack packaging process does a few things

  1. Read webpack. Config. Js
  2. Resolve file dependencies
  3. Replace require with __webpack_reuqire__
  4. {} is used locally to store all files, then __webpack_reuqire__ retrieves the file contents and executes the function

Plus loader plus plugin mechanism

Knowing the WebPack packaging process, we can try to implement a simple WebPack packer.

mypack

Create folder mypack, SRC /template.js and index.js.

template.js

! function(modules) { // webpackBootstrap // The module cache var installedModules = {}; // The require function function __my_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __my_require__); // Return the exports of the module Return module.exports; } // Load entry module and return exports return __my_require__("__entry__"); }({__modules_content__})Copy the code

A self-executing function passing __modules_content__ (this is to be replaced with a module object) implements a module cache queue, defines and returns the function __my_require__, and loads the module into the cache queue inside this function.

index.js

#! /usr/bin/env node const path = require('path') const fs = require('fs') // Default const defaultConfig={ Entry :'./ SRC /index.js', output:{filename:'bundle.js'}} // Get final configuration const config = {... defaultConfig, ... require(path.resolve('./mypack.config.js'))} class MyPack{ constructor(config){ this.config = config this.entry = config.entry this.root = process.cwd() this.modules={} this.template = '' } parse(code, __my_require__ let deps=[] let r = /require\('(.*)'\)/g code = code.replace(r, function(match, arg){ const retPath = path.join(parent, arg.replace(/'|"/g), '') deps.push(retPath) return `__my_require__('./${retPath}')` }) return {code, deps} } createModule(modulePath, Name){const fileContent = fs.readfilesync (modulePath, 'utF-8 ') // Replace code and dependent array const {code, deps} = this.parse(fileContent, path.dirname(name)) // console.log(code, deps) this.modules[name] = `function(module, exports, __my_require__){eval(\"${code}\")} 'deps.foreach ((dep)=>{this.createmodule (path.join(this.root,)) dep), './'+dep) }) } generateModuleStr(){ let fnTemp = '' Object.keys(this.modules).forEach(name => { fnTemp += `"${name}":${this.modules[name]},` }) return fnTemp } generateFiles(){ let template = fs.readFileSync(path.resolve(__dirname, './template.js'), 'utf8') this.template = template.replace('__entry__', this.entry) .replace('__modules_content__', this.generateModuleStr()) fs.writeFileSync('./dist/'+this.config.output.filename, Resolve (this.template) console.log(' write to file finished ')} start(){console.log(' start parsing dependencies ') const entryPath = path.resolve(this.root, this.entry) this.createModule(entryPath, This.entry) console.log(this.modules) // Generate this.generateFiles()}} const mypack=new mypack (config) mypack.start()Copy the code

We mainly do the following things:

  1. Read the configuration file, merge it with the default configuration, and get the final configuration
  2. Parse, parse the file contents require(xxx.js) of the format and replace it with ___require__, generating the replaced code and dependent array.
  3. CreateModule, which resolves dependencies from the entry file and generates module objects.
  4. GenerateFiles, generateFiles, based on template files, replace entry and modules_content. Generate the final package file.

npm link

How to use the mypack command in a project? In the local development of the NPM module, we can use the NPM link command to link the NPM module to the corresponding running project, convenient debugging and testing of the module.

  1. Add the bin command to package.json in the my-pack folder
"bin":{
    "my-pack": "./src/index.js"
}
Copy the code
  1. Run under my-packnpm linkTo install the current commands locally to the local global.
  2. Run my-pack in any file and the corresponding command will be executed.
  3. Add scripts in the webpack-demo folder
scripts:{
    ...
    "build:my-pack":"my-pack",
}
Copy the code
  1. performnpm run build:my-pack. Generate the final package file in the dist directory

So far, a simple hand-written wrapper has been implemented, the essence of Webpack is a JS module wrapper, understand this process, can better understand the official website webpack description:

In essence, Webpack is a static Module bundler for modern JavaScript applications. When WebPack works with an application, it recursively builds a Dependency graph containing every module the application needs, and then packages all of those modules into one or more bundles.

Everything is a module in Webpack, and webpack’s ability to handle style files, image resources, and any other non-javascript modules is implemented by loader. So in a complete webpack packer source code also need to add loader and plugin implementation. This article provides a basic framework and idea for understanding WebPack.