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
-
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.
-
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
-
Configure the bin script in package.json at the same level as the scripts
{ "bin": "./bin/webpack-theory.js" } Copy the code
-
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
- After success, yes
cd /usr/local/lib/node_modules
View all installed packages
- After success, yes
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
- Execute locally
webpack-theory
, will directlybin/webpack-theory.js
Console. 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
- 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.js
As a parametermoduleId
pass - Step 2: In the function
__webpack_require__
In execution- Will judge the current first
moduleId
Whether 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 simultaneouslyinstalledModules
neutralizationmodule
In the. The first executioninstalledModules
Is an empty object, moduleId is./src/index.js
. - perform
modules[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.js
In the js code.- The call passing scope is set to
module.exports
Because ofmodule.exports
Is an empty object, thenindex.js
The scope in is pointing to it, and this is also a classic example of using closures to solve the scope problem. module, module.exports
The function of the module is used to throw objects, the function is a, can refer torequire.js
To understand this__webpack_require__
The effect of the very clever, this entranceindex.js
Used 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.js
The bundle is not executed until the execution is complete.
- The call passing scope is set to
- Will judge the current first
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
- Get the WebPack configuration file
- Encapsulates a method for parsing configuration and simply packaging it
- Use abstract grammar books to parse module content
- Recursively resolve module dependencies
- 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 the
index.js
Is not directly usable, but is processed by parsing it into an abstract syntax tree, using a plug-in@babel/parser
Parse the module code into the AST, and then the plug-in@babel/traverse
The node of the AST is replaced. After the replacement is complete, the plug-in is used@babel/generator
Convert the AST into the original code of the modulerequire
become__webpack_require__
, it’s important to note that you need to do something with the path, because the path is relative tosrc
The following. processedindex
A recursive call is then required to handle all modules and claimbundle
Arguments 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?
-
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
- create
ejs
Template fileoutput.ejs
There 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
- create
-
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 one
parent.js
andchild.js
In 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 tell
handlerLoader1
Is the content to be replaced in the Loader handled by custom configuration? likeurl-loader
That passes a configuration optionoptions
And then accept and process it in the Loader. Can be achieved byloader-utils
thegetOptions
Extract from loaderoptions
For processing, the old version is passedthus.query
To 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
- if
handlerLoader1
The processed items need to be handed over to the next loader for processing, which will involve multiple peer loadershandlerLoader1
Make two copies and name themhandlerLoader11
andhandlerLoader12
The 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
- Reading a configuration file
webpack.config.js
themodule.rules
Loader configuration items to iterate backwards - The file type is matched according to the re, and the loader function is imported in batches
- All Loader functions are called in reverse iteration
- 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
- Install the @babel/ Parser plugin
npm i -S @babel/parser
Copy the code
- 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
- 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