Webpack has long been in the front end of the field shine, the use and optimization of WebPack has been a necessary skill for middle and senior engineers, on the basis of webpack principles to understand and master, will be in the future development. If you’re not familiar with WebPack, check out the previous article to learn more.

Due to my general ability and limited level, so I will be in the process of writing this article for some smelly and long repetitious, in order to make some students with relatively weak foundation can read more worry point, the next is to start the topic, I hope this article can be of some help to you.

  • Knowledge to prepare
    • Basic WebPack configuration
    • Webpack advanced configuration
    • Webpack optimization

Build the project

  1. Create a new folder webpack-theory

    Is the name of the later plug-in, which can be interpreted as an alias for WebPack and can be used directly in wepack-theory.

  2. Create a bin directory, create a webpack-theory.js file in this directory, and put the packaging tool main program in it

    At the top of the main program should be: #! /usr/bin/env Node identifier, which specifies the program execution environment as node

    #! /usr/bin/env node
    // logConsole. log('When linked via NPM link, type directly via webpack-theory directive');
    Copy the code
  3. Configure the bin script in package.json at the same level as the scripts

    {
     "bin": "./bin/webpack-theory.js"
    }
    Copy the code
  4. Use NPM Link to link the local webpack-theory project to the global package. After the link, you can directly use it locally for local testing. For details, see NPM Link

    1. After success, yescd /usr/local/lib/node_modulesView all installed packages

Webpack-theory is a symbolic link generated in the global node_modules directory to point to the local directory of webpack-theory. When local files (bin/webpack-theory) are modified, they are automatically linked to the global because global node_modules are only local references

  1. Execute locallywebpack-theory, will directlybin/webpack-theory.jsConsole. log of the
>>> Webpack-theory >>> When linked via the NPM link, you can type directly through the webpack-theory directiveCopy the code

Analysis of the bundle

Before you dive into the principles of WebPack, you need to know what the results of the files generated by the package look like. The files generated by the package can give you an overall understanding of what WebPack does in the process of processing the files, and the principles can be deduced from the results.

  • Create your own simple Weback project, create three JS files, index.js, parent-.js, and child.js, and package them through WebPack

    • Index. Js content
    const parent = require('./parent.js')
    
    console.log(parent)
    Copy the code
    • The parent. Js content
    const child = require('./child.js')
    
    module.exports = {
      msg: 'I am parent's message'.child: child.msg
    }
    Copy the code
    • Child. Js content
    module.exports = {
      msg: 'I am child's message'
    }
    Copy the code
  • NPX WebPack is used for packaging, after simple deletion and collation of the packaged files

(function (modules) { // Pass all modules as a modules object. The key is the path of the module and the value is the code inside the module
  // The module cache object, which contains the parsed path, can be used to determine whether the currently parsed module has been resolved
  var installedModules = {};

  // Define webPack's own require polyfill
  function __webpack_require__(moduleId) {

    // Check whether the moduleId is in the cache. If it is, no dependency resolution is required
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }

    // Create a new module and push it to the cache, so you can check if the dependencies have been resolved when you recursively iterate over them
    var module = installedModules[moduleId] = {
      i: moduleId, // moduleId is the key of the modules object, which is the argument of the self-executing function
      exports: {}
    };

    // Execute the modules[moduleId] function
    modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);

    // Return exports
    return module.exports;
  }

  // Pass the entry in the webpack.config.js configuration as the moduleId
  return __webpack_require__("./src/index.js");
})
/*** pass several modules in the project as arguments to self-executing functions
({
  // Webpack.config.js configures the value of the entry in the webpack.config.js configuration, which will be used as the entry to recursively resolve the dependency
  "./src/index.js": (function (module, exports, __webpack_require__) {
    eval("const parent = __webpack_require__(/*! ./parent.js */ \"./src/parent.js\")\n\nconsole.log(parent)\n\n//# sourceURL=webpack:///./src/index.js?");
  }),
  "./src/parent.js": (function (module, exports, __webpack_require__) {
    eval("const child = __webpack_require__(/*! ./child.js */ \"./ SRC /child.js\")\n\nmodule.exports = {\n MSG: 'I am parent ',\n child: child.msg\n}\n\n\n\n//# sourceURL=webpack:///./src/parent.js?");
  }),
  "./src/child.js": (function (module, exports) {
    eval("\ nmodule exports = {\ n MSG: 'I am a child of information' \ n} \ n \ n / / # sourceURL = webpack: / / /. / SRC/child. The js?"); })});Copy the code

According to the generated bundle.js, we can sort out the overall packaging idea of WebPack. We use a self-executing function to create a closure. Recursively resolved through the custom function __webpack_require__.

Briefly analyze the overall implementation of the bundle

  1. Step 1: The first time a self-executing function is executed, it runs the internal function directly__webpack_require__Function and will enter the path of the file./src/index.jsAs a parametermoduleIdpass
  2. Step 2: In the function__webpack_require__In execution
    1. Will judge the current firstmoduleIdWhether the cache already existsinstalledModules, if it exists, it will be returned directly without any further parsing of its dependency. If it does not exist, an object is constructed and stored simultaneouslyinstalledModulesneutralizationmoduleIn the. The first executioninstalledModulesIs an empty object, moduleId is./src/index.js.
    2. performmodules[moduleId]Function, that is, to executemodules['./src/index.js'], which is scoped and passed through a callmodule, module.exports, __webpack_require__Three parameters that execute the entry file module./src/index.jsIn the js code.
      1. The call passing scope is set tomodule.exportsBecause ofmodule.exportsIs an empty object, thenindex.jsThe scope in is pointing to it, and this is also a classic example of using closures to solve the scope problem.
      2. module, module.exportsThe function of the module is used to throw objects, the function is a, can refer torequire.jsTo understand this
      3. __webpack_require__The effect of the very clever, this entranceindex.jsUsed in therequire('./parent.js')Has been replaced by__webpack_require__("./src/parent.js\"), the implementation ofmodules[moduleId]Function is called from here__webpack_require__Function is called recursively, and it goes back to step 2 again untilchild.jsThe bundle is not executed until the execution is complete.

After analyzing the bundle, it can be found that for the packaging result of WebPack, except that the parameter modules will change with the business logic of the code, the code in the self-executing function is always fixed. Therefore, when you want to write a WebPack of your own, The important thing to look at and work out is how modules is generated.

Create a bundle

After analyzing the webPack bundle files, it will be much easier to implement the result-oriented backtracking process. If we want to implement a simple version of WebPack ourselves, we will have some ideas.

First, you need a simple wBePack configuration

const path = require('path')
module.exports = {
  entry: './src/index.js'.output: {path: path.join(__dirname, 'dist'),
    filename: 'bundle.js'}}Copy the code

Simple version of webPack implementation ideas

  1. Get the WebPack configuration file
  2. Encapsulates a method for parsing configuration and simply packaging it
    1. Use abstract grammar books to parse module content
    2. Recursively resolve module dependencies
    3. Use the template engine to output the results

Once you have the idea, the next step is to implement it step by step

  • Get the WebPack configuration file, and all you need to do is parse the configuration file and package it to generate the bundle. The first is to read the configuration file that you need to package the project
const config = require(path.resolve('webpack.config.js'))
Copy the code
  • Once the configuration file is obtained, how do you parse and implement webPack’s capabilities, all encapsulated in the Compiler class, to parse the configuration of the configuration file and start parsing with Start
const Compiler = require('.. /lib/Compiler')
new Compiler(config).start()
Copy the code
  • The key is to implement this method, define a Compiler class, provide a start method to start the WebPack package, and use depAnalyse to get the contents of the entry file index
const path = require('path')
const fs = require('fs')

class Compiler {
  constructor(config){
    this.config = config
    const { entry } = config // Configuration file
    this.entry = entry // Import file
    this.root = process.cwd() // Enter the webpack-theory path
    this.modules = {} // Initialize a controller to hold all modules
  }

  /** * start packaging * the main part of packaging is the analysis of dependencies */
  start(){
    this.depAnalyse(path.resolve(this.root, this.entry))
  }

  /** * Dependency analysis * needs to start analysis based on entry */
  depAnalyse(modulePath){
    // Get the contents of index.js
    let source = this.getSource(modulePath)
  }

  // Read the file
  getSource(path){
    return fs.readFileSync(path, 'utf-8')}}module.exports = Compiler
Copy the code
  • Access to theindex.jsIs not directly usable, but is processed by parsing it into an abstract syntax tree, using a plug-in@babel/parserParse the module code into the AST, and then the plug-in@babel/traverseThe node of the AST is replaced. After the replacement is complete, the plug-in is used@babel/generatorConvert the AST into the original code of the modulerequirebecome__webpack_require__, it’s important to note that you need to do something with the path, because the path is relative tosrcThe following. processedindexA recursive call is then required to handle all modules and claimbundleArguments to the self-executing function inmodules

At this point, after the module code for index is processed, it becomes the required code

const parent = __webpack_require__("./src/parent.js");
console.log(parent);
Copy the code

Add the following to the depAnalyse function

// Get the contents of index.js
let source = this.getSource(modulePath) 
// -------

// Prepare an array of dependencies to store the current module
let dependenceArr = []
// Parse js code into AST
let ast = parser.parse(source)

// Replace the require in the AST with __webpack_require__
traverse(ast, {
  // p is the node of the abstract syntax tree
  CallExpression(p) {
    if (p.node.callee.name === 'require') {
      // Replace require with __webpack_require__ in the code
      p.node.callee.name = '__webpack_require__'
      const oldValue = p.node.arguments[0].value
      // Change the path to avoid backslash \ on Windows
      p.node.arguments[0].value = ('/' + path.join('src', oldValue)).replace(/\\+/g.'/')

      // Every time a require call is found, add the path to the dependency array after modification
      dependenceArr.push(p.node.arguments[0].value)
    }
  }
})

// Build modules objects
const sourceCode = generator(ast).code
const modulePathRelative = '/' + (path.relative(this.root, modulePath)).replace(/\\+/g.'/')
this.modules[modulePathRelative] = sourceCode

// A recursive call loads all dependencies
dependenceArr.forEach(dep= > this.depAnalyse(path.resolve(this.root, dep)))
Copy the code

Now that we’ve finished processing modules, the next thing we need to deal with is how to generate bundle.js. The bundle analysis has already pointed out that the only thing we need to focus on is the splicing of modules

  • How do you use the template engine to package a module’s code?
  1. The template engine EJS is used to create the template. The content of the template is the content generated by the WebPack package. You only need to iterate according to the Compiler modules. It is also important to note that the return __webpack_require__(__webpack_require__.s = “<%-entry%>”) is passed in as the entry to the configuration file and as an argument to the first execution of the self-executing function

    1. createejsTemplate fileoutput.ejsThere are only two things to focus on. The default code is used for the rest
    Return __webpack_require__(__webpack_require__.s = "<%-entry%>"); Modules {<% for (let k in modules) {%> "<%-k%>": (function (module, exports, __webpack_require__) { eval(`<%-modules[k]%>`); % > < %}}}),Copy the code
  2. Add an emitFile method to the Compiler, which generates bundles from the template and is called after depAnalyse in the start function

    /** * create a file based on the template */
    emitFile(){
      // The ejS template has been created
      const template = this.getSource(path.join(__dirname, '.. /template/output.ejs'))
      // Compile using ejs
      const result = ejs.render(template, {
        entry: this.entry,
        modules: this.modules
      })
    
      // Get the output path and file name
      const {
        path: filePath,
        filename
      } = this.output
      const outputPath = path.join(filePath, filename)
    
      // Generate the bundle and place it in the specified directory
      fs.writeFile(outputPath, result, (err) => {
        console.log(err ? err : 'Bundle generation complete'); })}Copy the code

So far, simple module packaging has been possible. You can simply package index.js, parent-.js, and child.js, but only the simplest WebPack usage is supported

loader

Loader is one of the important core functions of Webpack and is used frequently. Its main function is to process the code according to the expected results and generate the final code for output. Therefore, it is necessary to master the basic mechanism of Loade. Loader is also very simple to use, and its basic configuration and usage will not be discussed here. Let’s look at how to add a parsing Loader to your Webpack-theory and how to write your own loader.

Homemade loader

Before adding the ability to process loaders to Webpack-theory, let’s see how we can implement our own Loader in WebPack

  • Webpack loader, the main steps are as follows

    • Read the module.rules configuration item of the webpack.config.js configuration file and iterate in reverse order (each matching rule of rules matches in reverse order).
    • The file type is matched according to the re, and the loader function is imported in batches
    • The default is to call all Loader functions iteratively in reverse order (loader from right to left, bottom to top), or you can control the order yourself
    • Finally, return the processed code
  • Loader is configured when you want to add the ability to process CASS files to WebPack

{
  test:/\.scss$/.use: ['style-loader'.'css-loader'.'sass-loader']}Copy the code

Sass-loader is a function that reads the contents of a file ending in. SCSS according to test rules. It then passes the matched contents to a function that processes the contents of the sass file into CSS that can be recognized by the browser. So loader is essentially a function that takes a parameter, which is the code in the matched file. The same goes for CSS-Loader and style-Loader, but they do different things internally.

function handlerScss(sourceCode){
  // The content of the SCSS file is processed according to the rules. The result is that the browser can recognize the parsed CSS and return it
  return newSourceCode
}
Copy the code
  • The next step is to implement a simple loader of its own that will be the previous oneparent.jsandchild.jsIn theinformationIs processed by using the Loadermsg
// Replace the message in the js file with MSG
module.exports = function (source) {
  return source.replace(/ information/g.'msg')}Copy the code

Configure loader in WebPack

{
  test:/\.js/.use: ['./loader/handlerLoader1.js']}Copy the code

After packaging with NPX Webpack, you can see that the message in the original code has been replaced with MSG in the packaged code

  • If want to tellhandlerLoader1Is the content to be replaced in the Loader handled by custom configuration? likeurl-loaderThat passes a configuration optionoptionsAnd then accept and process it in the Loader. Can be achieved byloader-utilsthegetOptionsExtract from loaderoptionsFor processing, the old version is passedthus.queryTo process

Example Modify the loader file handlerLoader1

const loaderUtils = require('loader-utils')
// Replace the information in the js file with the name passed by options
module.exports = function (source) {
  const optionsName = loaderUtils.getOptions(this).name || ' '
  return source.replace(/ information/g, optionsName)
}
Copy the code

Modify the WebPack loader

{
  test:/\.js/.use: {loader: './loader/loader1.js'.options: {name:'New Information'}}}Copy the code

Once packaged with NPX WebPack, you can replace it with the Options configuration

  • ifhandlerLoader1The processed items need to be handed over to the next loader for processing, which will involve multiple peer loadershandlerLoader1Make two copies and name themhandlerLoader11andhandlerLoader12The contents can be kept as they are, but the names of the corresponding Loader files are printed separately in the original function, because it is only to see the loading of the loader.

The contents of handlerLoader1 are

// Replace the message in the js file with MSG
module.exports = function (source) {
  console.log('I am handlerLoader1'); // The logs of the other two loaders are handlerLoader2 handlerLoader3
  return source.replace(/ information/g.'msg')}Copy the code

Webpack configuration loader

{
  test:/\.js/.use: ['./loader/handlerLoader1.js'.'./loader/handlerLoader2.js'.'./loader/handlerLoader3.js']}Copy the code

The webPack package is executed and the output shows that the loader’s default order is right to left

>>> I'm handlerLoader3 >>> I'm handlerLoader2 >>> I'm handlerLoader1Copy the code
  • If the Webpack loader is changed to
{
  test:/\.js/.use: ['./loader/loader1.js'] {},test:/\.js/.use: ['./loader/loader2.js'] {},test:/\.js/.use: ['./loader/loader3.js']},Copy the code

The webPack package is executed and the output shows that the loader’s default order is bottom-up

>>> I'm handlerLoader3 >>> I'm handlerLoader2 >>> I'm handlerLoader1Copy the code

Adding a Loader

After making a loader, we can summarize the functions of WebPack to support Loader, which is mainly 4 steps

  1. Reading a configuration filewebpack.config.jsthemodule.rulesLoader configuration items to iterate backwards
  2. The file type is matched according to the re, and the loader function is imported in batches
  3. All Loader functions are called in reverse iteration
  4. Returns the processed code

Adding the ability to process loader in Webpack-theory is nothing more than to match the required resources according to the regularization of configured rules when loading each module, and then load and use the corresponding Loader to process and call iteratively when the conditions are met

It should be noted that when to execute the loader, each time when the module dependency is obtained, the test of loader needs to be matched. If the test matches, the corresponding loader will be loaded for processing. For example, there are three JS files in the case code of this article. First, index.js will be loaded. Before loading and parsing the dependency of index, it needs to be reversed to facilitate all loaders. Since index introduces parent.js, the next thing to do is to determine and deal with it before recursively calling the depAnalyse method to parse parnet, and the same goes for child.js.

Add the following code to the depAnalyse method before each parse:

// Internally define a function that handles the loader
const _handleLoader = (usePath, _this) = > {
  const loaderPath = path.join(this.root, usePath)
  const loader = require(loaderPath)
  source = loader.call(_this, source)
}

// Read the rules rule and iterate in reverse order
const rules = this.rules
for (let i = rules.length - 1; i >= 0; i--) {
  const {
    test,
    use
  } = rules[i]

  // Match whether the modulePath complies with the rules. If it complies with the rules, you need to iterate backwards to obtain all loaders
  // Get each rule and match it to the current modulePath
  if (test.test(modulePath)) {
    // Use can be an array, object, or string
    console.log(use);
    
    if (Array.isArray(use)) {
      // array
      for (let j = use.length - 1; j >= 0; j--) {
        // const loaderPath = path.join(this.root, use[j])
        // const loader = require(loaderPath)
        // source = loader(source)
        _handleLoader(use[j])
      }
    } else if (typeof use === 'string') {
      // string
      _handleLoader(use)
    } else if (use instanceof Object) {
      // object
      _handleLoader(use.loader, {
        query: use.options
      })
    }
  }
}
Copy the code

Loader basic related writing so far, but still need a lot of practice thinking, here is only the simplest demonstration, You can refer to the official documents for loader enforce, asynchronous Loader and other knowledge in-depth study and view Babel, Sass-Loader and other community excellent loader in-depth understanding and practice.

plugin

Plug-ins are an important part of the WebPack ecosystem and provide a powerful way for the community to tap directly into the WebPack compilation process. Plug-ins can hook into all the critical events that are triggered during each compilation. At each step of compilation, the plug-in has full access to the Compiler object and, if appropriate, to the current Compilation object.

Custom plugins are essentially programmed to implement functionality within the lifecycle hooks provided by the WebPack build process, and do what needs to be done at the appropriate time. For example, the clean-webpack-plugin executes the plug-in before the build, emptying the package directory.

Homemade plugin

  • Before implementing your own plug-in, let’s take a look at what webPack plug-ins consist of

    • A JavaScript named function

    • Define an apply method on the prototype of the plug-in function

    • Specifies an event hook that is bound to webPack itself

    • Handle specific data for webPack internal instances

    • When the functionality is complete, call the callback provided by WebPack

  • Lifecycle hooks for WebPack

The Compiler module is the backbone engine of WebPack and creates a Compilation instance using all the options passed through the CLI or Node API. It extends the Tapable class to register and invoke the plug-in. Most user-facing plug-ins are registered with Compiler first.

hello word

According to the official document to implement a Hello Word plug-in, you can simply understand the plugin

// 1. A JavaScript named function
// 2. Define an apply method on the prototype of the plug-in function
class HelloWordPlugin {
  // 3. There is a compiler parameter in apply
  apply(compiler){
    console.log('Plug-in executed');
    // 4. The compiler object is used to register events, and all hooks are available
    // Register a compiled hook with the plugin name as the event name
    compiler.hooks.done.tap('HelloWordPlugin', (stats) => {
      console.log('The whole WebPack package is over');
    })

    compiler.hooks.emit.tap('HelloWordPlugin', (compilation) => {
      console.log('Emit method'); }}})module.exports = HelloWordPlugin
Copy the code

Introduced and used in webpack.config.js

const HelloWordPlugin = require('./plugins/HelloWordPlugin')

{
  // ... 
  plugins:[
    new HelloWordPlugin()
  ]
}
Copy the code

NPX WebPack, you can see the trigger of the plug-in

>>> The plugin executes >>> triggers the Emit method >>> the entire WebPack packaging is finishedCopy the code

HtmlWebpackPlugin

Imitate the HtmlWebpackPlugin plug-in function

Html-webpack-plugin can automatically import bundle.js by copying the specified HTML template to the dist directory

  • Implementation steps
    • Write a custom plug-in and register the afterEmit hook
    • The HTML template is read based on the Template property passed in when the object is created
    • Use a tool to analyze HTML. Cheerio is recommended. You can use the jQuery API directly
    • Loop through the list of webPack-packed resource files, packing all bundles if there are multiple
    • Output the newly generated HTML string into the dist directory
const path = require('path')
const fs = require('fs')
const cheerio = require('cheerio')

class HTMLPlugin {
  constructor(options){
    // Plug-in parameters, such as filename, template, etc
    this.options = options
  }

  apply(compiler){
    // 1. Register afterEmit hooks
    // If you use the done hook, you need to use stats.compilation.assets and it will be later than afterEmit
    compiler.hooks.afterEmit.tap('HTMLPlugin', (compilation) => {
      // 2. Read the CONTENT of the HTML file according to the template
      const result = fs.readFileSync(this.options.template, 'utf-8')
      
      // 3. Parse HTML using Cheerio
      let $ = cheerio.load(result)

      // 4. Insert the script tag into the HTML
      Object.keys(compilation.assets).forEach(item= >{$(`<script src="/${item}"></script>`).appendTo('body')})// 5. Convert to new HTML and write to dist directory
      fs.writeFileSync(path.join(process.cwd(), 'dist'.this.options.filename), $.html())
    })
  }
}

module.exports = HTMLPlugin
Copy the code
  • Note the difference between Compiler and Compilattion
    • Compile: The compile object represents the unchanging WebPack environment and is specific to WebPack
    • Compilation: The Compilation object is for project files that change from time to time. Whenever the files change, the compilation object is recreated

Add plugin function

To add plugin to Webpack-theory, you only need to create the corresponding hook during Compiler construction. Webpack-theory is only responsible for defining the hook and triggering it at the appropriate time. As for the event registration of the hook, each plugin is implemented internally.

// Tapable's constructor defines hooks internally
this.hooks = {
  afterPlugins: new SyncHook(),
  beforeRun: new SyncHook(),
  run: new SyncHook(),
  make: new SyncHook(),
  afterCompiler: new SyncHook(),
  shouldEmit: new SyncHook(),
  emit: new SyncHook(),
  afterEmit: new SyncHook(['compilation']),
  done: new SyncHook(),
}

// Trigger the apply method for all plugins and pass in the Compiler object
if(Array.isArray(this.config.plugins)){
  this.config.plugins.forEach(plugin= > {
    plugin.apply(this)}); }Copy the code

You can call the call method of the corresponding hook at an appropriate time. If you need to pass in parameters, you can define the parameters in the corresponding hook and pass them directly during the call

Call the hook defined in start

start() {
  this.hooks.compiler.call() // Start compiling
  this.depAnalyse(path.resolve(this.root, this.entry))
  this.hooks.afterCompiler.call() // The compilation is complete
  this.hooks.emit.call() // Start firing files
  this.emitFile()
  this.hooks.done.call() / / finish
}
Copy the code

supplement

AST

It parses a line of code into an object format, which can be viewed using an online tool that generates an AST syntax tree called AstExplorer

  1. Install the @babel/ Parser plugin
npm i -S @babel/parser
Copy the code
  1. use
const parser = require('@babel/parser')
// Source is the code snippet needed to generate the AST syntax tree
const ast = parser.parse(source)
Copy the code
  1. Generation effect

The source code

const news = require('./news')
console.log(news.content)
Copy the code

The generated AST syntax tree

Node {
  type: 'File',
  start: 0,
  end: 57,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 3, column: 25 } },
  program:
   Node {
     type: 'Program',
     start: 0,
     end: 57,
     loc: SourceLocation { start: [Position], end: [Position] },
     sourceType: 'script',
     interpreter: null,
     body: [ [Node], [Node] ],
     directives: [] },
  comments: [] }
Copy the code

tabable

The core of the event flow mechanism within WebPack is tapable, which connects various plug-ins in the form of event flow. Tapable is similar to the Events library in Node, and its core principle is a subscription publishing pattern

  • Basic usage

    • Define the hook
    • User registration event
    • The hook is called at the appropriate stage, and the event is fired
    const { SyncHook } = require('tapable')
    /** * Learning front end * Learning process 1. Prepare 2. Learn HTML 3. Learn CSS 4. Learn JS 5. Learn framework * Each process of learning is similar to the life cycle */
    class Frontend{
    
      constructor() {// 1. Define the lifecycle hook
        this.hooks = {
          beforeStudy: new SyncHook(),
          afterHtml: new SyncHook(),
          afterCss: new SyncHook(),
          afterJs: new SyncHook(),
          // The parameters to be passed need to be defined in new SyncHook()
          afterFrame: new SyncHook(['name']),
        }
      }
    
      study(){
        // 3. Call when appropriate
        console.log('准备');
        this.hooks.beforeStudy.call()
        console.log('Get ready to learn HTML');
        this.hooks.afterHtml.call()
        console.log('Get ready to learn CSS');
        this.hooks.afterCss.call()
        console.log('Ready to learn JS');
        this.hooks.afterJs.call()
        console.log('Preparation Framework');
        this.hooks.afterFrame.call('the vue, the react)}}const f = new Frontend()
    
    // 2. Register the event
    f.hooks.afterHtml.tap('afterHtml', () = > {console.log('Learned HTML, completed part of the front-end.');
    })
    
    f.hooks.afterJs.tap('afterJs', () = > {console.log('Learned JS, completed part of the front-end knowledge');
    })
    
    f.hooks.afterFrame.tap('afterFrame', (name) => {
      console.log(` learned${name}Frame, the world is invincible.... `);
    })
    
    
    f.study()
    Copy the code

    Demo code

    Principle code Code used with principle