Webpack construction process

Sir Huang 2021-05-03 20:53:28
webpack construction process


This article also comes from the course notes of Lagou education , To understand webpack Packaging process

1、 Input... At the terminal npx webpack perhaps yarn webpack Perform packaging , At this point, it will find and execute node_modules/.bin/webpack.cmd

2、 perform webpack.cmd It's actually execution webpack/bin/webpack.js

3、 stay webpack/bin/webpack.js China will

require(webpack-cli/bin/cli.js)
 Copy code 

Which is execution cli.js This file , This file contains a self calling function , That's the beginning webpack Packaging process

4、 stay cli.js It will be dealt with first options And the parameters in the command line , That is to set the user's options And default options And the command line parameters are merged and passed through

const webpack = require("webpack");
 Copy code 

introduce webpack/lib/webpack Export of webpack function , And then execute this function

5、 perform webpack(options) return compiler, among webpack Function steps

  1. Instantiation compiler object

    • hold options Upper context Hang up compiler For instance
    • initialization hooks object , That is to initialize the corresponding hook( Lifecycle hook ), The core lifecycle hooks include :
    entryOption -> beforeRun -> run -> beforCompile -> compile -> thisCompilation -> compilation -> make -> afterCompile -> emit
  2. Incoming options Mount to compiler On

  3. initialization NodeEnvironmentPlugin( Give Way compiler Ability to read and write specific documents )

new NodeEnvironmentPlugin().apply(compiler)
 Copy code 
  1. take options Upper piugin The plug-in is mounted to compiler On , That is, traversal execution plugin.apply Method

 if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler)
}
}
 Copy code 
  1. mount webpack Built in plug-ins , perform entryOption hook , take compilation.addEntry() Method to mount to make On the hook

 // webpack.js
new WebpackOptionsApply().process(options, compiler);
 Copy code 
 // WebpackOptionsApply.js
class WebpackOptionsApply {
// process The aim is to compilation.addEntry() Method to mount to make On the hook 
process(options, compiler) {
new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
 Copy code 

6、 perform compiler.run Method

  1. The parameter callback Assign a value to a variable finalCallback

  2. Defining variables onCompiled, It's going to be done here emitAssets, That's what will be dealt with chunk Write to the specified file and output to dist

  3. Execute sequentially beforeRun、run Lifecycle hook , And in run Life cycle done Call back to execute compiler.compile Method

 this.hooks.beforeRun.callAsync(this, (err) => {
this.hooks.run.callAsync(this, (err) => {
this.compile(onCompiled)
})
})
 Copy code 

7、 perform compiler.compile Method

  1. call compiler.newCompilationParams Initialize variable params

 newCompilationParams() {
// Give Way params Have the ability to create modules 
const params = {
normalModuleFactory: new NormalModuleFactory()
}
return params
}
 Copy code 
  1. Execute sequentially beforeCompile、compile Lifecycle hook

  2. After execution compile After the life cycle hook , call compiler.newCompilation(params) initialization compilation, stay newCompilation Methods are also executed thisCompilation,compilation Lifecycle hook

    • stay Compilation In the constructor of compilation mount compiler.... A lot of attributes
 // Compiler.js
newCompilation(params) {
const compilation = this.createCompilation()
this.hooks.thisCompilation.call(compilation, params)
this.hooks.compilation.call(compilation, params)
return compilation
}
createCompilation() {
return new Compilation(this)
}
// Compilation.js
class Compilation extends Tapable {
constructor(compiler) {
super()
this.compiler = compiler
this.context = compiler.context
this.options = compiler.options
// Give Way compilation Ability to read and write documents 
this.inputFileSystem = compiler.inputFileSystem
this.outputFileSystem = compiler.outputFileSystem
this.entries = [] // An array of all entry modules 
this.modules = [] // Store the data of all modules 
this.chunks = [] // Store the output of the current packaging process chunk
this.assets = []
this.files = []
this.hooks = {
succeedModule: new SyncHook(['module']),
seal: new SyncHook(),
beforeChunks: new SyncHook(),
afterChunks: new SyncHook()
}
}
 Copy code 
  1. Generate compilation After execution make Lifecycle hook

    • It's here to execute entryOption The hook runs to make On the hook compilation.addEntry Method , The method accepts 4 Parameters context, entry, name, callback
    • addEntry In the call compilation._addModuleChain Method
    • _addModuleChain In the call compilation.createModule Method ,createModule The order of execution is as follows :
      1. adopt normalModuleFactory Create a module, That is, the module used to load the entry file
      1. initialization afterBuild Method , Here, the dependent module loading of the currently created module will be performed
      1. Called compilation.buildModule(module, afterBuild), This is going to call module.build() Method
        • 01 Read from the file to be loaded in the future module Content , This
        • 02 If not now js Modules need Loader To deal with , Eventually return js modular
        • 03 After the above operation is completed, you can js The code changes to ast Grammar tree
        • 04 At present js Many other modules may be referenced inside the module , So we need to do it recursively
        • 05 After the front is finished , We just need to repeat it
      1. perform doAddEntry Method , That is to push the currently created entry file module compilation.entries in
      1. Push the current entry file module compilation.modules in
      1. Performs loading of dependent modules , That is, the above steps 3-5
  2. After execution make After the life cycle hook , perform make Crocheted done Callback , This will be done compilation.seal() Method , Its specific implementation is as follows

    • perform compilation Inside seal and beforeChunks hook
    • Traverse compilation.entries, And merge each of the entry modules and its dependent modules into one chunk, And push through compilation.chunks Array
    • call compilation.createChunkAssets Generate the final code
  3. call compilation.emitAssets That's execution compiler.run Methods onCompiled

 // Compilation.js
/**
* Complete the module compilation operation
* @param {*} context The root of the current project
* @param {*} entry The relative path of the current entry
* @param {*} name chunkName main
* @param {*} callback Callback
*/
addEntry(context, entry, name, callback) {
this._addModuleChain(context, entry, name, (err, module) => {
callback(err, module)
})
}
_addModuleChain(context, entry, name, callback) {
this.createModule({
parser,
name: name,
context: context,
rawRequest: entry,
resource: path.posix.join(context, entry),
moduleId: './' + path.posix.relative(context, path.posix.join(context, entry))
}, (entryModule) => {
this.entries.push(entryModule)
}, callback)
}
/**
* Define a method to create a module , Achieve reuse
* @param {*} data Some attribute values needed to create a module
* @param {*} doAddEntry Optional parameters , When loading the entry module , The entrance module's id write in this.entries
* @param {*} callback
*/
createModule(data, doAddEntry, callback) {
let module = normalModuleFactory.create(data)
const afterBuild = (err, module) => {
// stay afterBuild We need to judge , Current time module Whether to handle dependency loading after loading 
if (module.dependencies.length > 0) {
// The current logic means module There are modules that need to depend on Loading , So we can define a separate method to implement 
this.processDependencies(module, (err) => {
callback(err, module)
})
} else {
callback(err, module)
}
}
this.buildModule(module, afterBuild)
// When we finished this time build After the operation will module Preservation 
doAddEntry && doAddEntry(module)
this.modules.push(module)
}
/**
* Complete specific build Behavior
* @param {*} module The current module to be compiled
* @param {*} callback
*/
buildModule(module, callback) {
module.build(this, (err) => {
// If the code goes here, it means the current Module The compilation of is complete 
this.hooks.succeedModule.call(module)
callback(err, module)
})
}
processDependencies(module, callback) {
// 01 The core function of the current function is to realize the recursive loading of a dependent module 
// 02 The idea of loading a module is to create a module , Then find a way to bring in the content of the loaded module ?
// 03 At the moment we don't know module You need to rely on several modules , At this point, we need to find a way to load all the dependent modules before executing callback?【 neo-async 】
let dependencies = module.dependencies
async.forEach(dependencies, (dependency, done) => {
this.createModule({
parser,
name: dependency.name,
context: dependency.context,
rawRequest: dependency.rawRequest,
moduleId: dependency.moduleId,
resource: dependency.resource
}, null, done)
}, callback)
}
seal(callback) {
this.hooks.seal.call()
this.hooks.beforeChunks.call()
// 01 Currently, all entry modules are stored in compilation Object's entries In the array 
// 02 So called encapsulation chunk It means according to an entrance , And then find all its dependencies , Put their source code together , And then merge 
for (const entryModule of this.entries) {
// The core : Create a module and load the contents of an existing module , Record the module information at the same time 
const chunk = new Chunk(entryModule)
// preservation chunk Information 
this.chunks.push(chunk)
// to chunk Attribute assignment 
chunk.modules = this.modules.filter(module => module.name === chunk.name)
}
// chunk After sorting out the process, we enter into chunk Code processing ( Template file + The source code in the module ==》chunk.js)
this.hooks.afterChunks.call(this.chunks)
// Generate code content 
this.createChunkAssets()
callback()
}
createChunkAssets() {
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i]
const fileName = chunk.name + '.js'
chunk.files.push(fileName)
// 01 Get the path of the template file 
let tempPath = path.posix.join(__dirname, 'temp/main.ejs')
// 02 Read the contents of the module file 
let tempCode = this.inputFileSystem.readFileSync(tempPath, 'utf8')
// 03 Get render function 
let tempRender = ejs.compile(tempCode)
// 04 Press ejs The syntax of rendering data 
let source = tempRender({
entryModuleId: chunk.entryModule.moduleId,
modules: chunk.modules
})
// The output file 
this.emitAssets(fileName, source)
}
}
emitAssets(fileName, source) {
this.assets[fileName] = source
this.files.push(fileName)
}
 Copy code 
// NormalModule.js
class NormalModule {
constructor(data) {
this.context = data.context
this.name = data.name
this.moduleId = data.moduleId
this.rawRequest = data.rawRequest
this.parser = data.parser // TODO: Waiting for completion 
this.resource = data.resource
this._source // Store the source code of a module 
this._ast // Store a template source code corresponding to ast
this.dependencies = [] // Define an empty array to store the module information loaded by dependency 
}
build(compilation, callback) {
/**
* 01 Read from the file to be loaded in the future module Content , This
* 02 If not now js Modules need Loader To deal with , Eventually return js modular
* 03 After the above operation is completed, you can js The code changes to ast Grammar tree
* 04 At present js Many other modules may be referenced inside the module , So we need to do it recursively
* 05 After the front is finished , We just need to repeat it
*/
this.doBuild(compilation, (err) => {
this._ast = this.parser.parse(this._source)
// there _ast Is the current module Syntax tree , We can modify it , Finally, the ast Turn back to code Code 
traverse(this._ast, {
CallExpression: (nodePath) => {
let node = nodePath.node
// location require The node 
if (node.callee.name === 'require') {
// Get the original request path 
let modulePath = node.arguments[0].value // './title'
// Retrieve the name of the currently loaded module 
let moduleName = modulePath.split(path.posix.sep).pop() // title
// [ Currently our packer only deals with js ]
let extName = moduleName.indexOf('.') == -1 ? '.js' : ''
moduleName += extName // title.js
// 【 Finally we want to read the current js Contents of Li 】 So we need an absolute path 
let depResource = path.posix.join(path.posix.dirname(this.resource), moduleName)
// 【 Will be the current module of id Definition OK】
let depModuleId = './' + path.posix.relative(this.context, depResource) // ./src/title.js
// Record the information of the currently dependent module , Convenient to load recursively 
this.dependencies.push({
name: this.name, // TODO: It needs to be changed in the future 
context: this.context,
rawRequest: moduleName,
moduleId: depModuleId,
resource: depResource
})
// replace content 
node.callee.name = '__webpack_require__'
node.arguments = [types.stringLiteral(depModuleId)]
}
}
})
// The above operation is to use ast Made code changes as required , The following is the use of .... Will modify the ast Turn back to code
let { code } = generator(this._ast)
this._source = code
callback(err)
})
}
doBuild(compilation, callback) {
this.getSource(compilation, (err, source) => {
this._source = source
callback()
})
}
getSource(compilation, callback) {
compilation.inputFileSystem.readFile(this.resource, 'utf8', callback)
}
}
 Copy code 

7、 perform compiler.emitAssets() Methods and compiler.run() Callbacks passed in during execution

  • compiler.emitAssets Call back compiler Of emit The life cycle hook is basically over at this time
 emitAssets(compilation, callback) {
// The core of what needs to be done right now : 01 establish dist 02 Write the file after the directory is created 
// 01 Define a tool method to perform file generation operations 
const emitFlies = (err) => {
const assets = compilation.assets
let outputPath = this.options.output.path
for (let file in assets) {
let source = assets[file]
let targetPath = path.posix.join(outputPath, file)
this.outputFileSystem.writeFileSync(targetPath, source, 'utf8')
}
callback(err)
}
// After creating the directory, start the file writing 
this.hooks.emit.callAsync(compilation, (err) => {
mkdirp.sync(this.options.output.path)
emitFlies()
})
}
 Copy code 
版权声明
本文为[Sir Huang]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/05/20210503204934006R.html

  1. Analysis of MVC
  2. [middle stage] please stay and join me in the backstage
  3. Understanding front end garbage collection
  4. [continuous update] front end special style implementation
  5. Flutter product analysis and package reduction scheme
  6. XPath positioning
  7. 前端开发css中的flex布局的使用
  8. The use of flex layout in front end development CSS
  9. JQuery核心函数和静态方法
  10. JQuery core functions and static methods
  11. Node family - understanding of blocking and non blocking
  12. 热点微前端Microfrontend的讨论:谷歌AdWords是真实的微前端
  13. Vue source code analysis (2) initproxy initialization proxy
  14. What's TM called react diff
  15. Summary of common front end data structure
  16. Useeffect in hooks
  17. [encapsulation 02 design pattern] Command pattern, share meta pattern, combination pattern, proxy pattern, strategy pattern
  18. Front end notes: virtual Dom and diff of vue2. X
  19. The best code scanning plug-in of flutter
  20. The simplest plug-in for rights management of flutter
  21. 21. Object oriented foundation "problems and solutions of object traversal"
  22. Discussion on hot micro front end: Google AdWords is a real micro front end
  23. Usecallback and usememo for real performance optimization
  24. 【前端圭臬】十一:从规范看 JavaScript 执行上下文(下)
  25. [front end standard] 11: Javascript execution context from the perspective of specification (2)
  26. Hexagonal六角形架构ReactJS的实现方式 - Janos Pasztor
  27. Transaction of spring's reactive / imperative relational database
  28. The implementation of hexagonal hexagonal reactjs Janos pasztor
  29. HTTP状态码:402 Payment Required需要付款 - mozilla
  30. HTTP status code: 402 payment required - Mozilla
  31. Factory mode, constructor mode and prototype mode
  32. Build the scaffold of react project from scratch (Series 1: encapsulating a request method with cache function based on Axios)
  33. Cocos Quick Start Guide
  34. Comparison of three default configurations of webpack5 modes
  35. A case study of the combination of flutter WebView and Vue
  36. CSS: BFC and IFC
  37. A common error report and solution in Vue combat
  38. JS: this point
  39. JS: prototype chain
  40. JavaScript series -- promise, generator, async and await
  41. JS: event flow
  42. Front end performance optimization: rearrangement and redrawing
  43. JS - deep and shallow copy
  44. JavaScript异步编程3——Promise的链式使用
  45. JavaScript asynchronous programming 3 -- chain use of promise
  46. Vue.js组件的使用
  47. The use of vue.js component
  48. How to judge whether a linked list has links
  49. Element UI custom theme configuration
  50. Text image parallax effect HTML + CSS + JS
  51. Spring的nohttp宣言:消灭http://
  52. Vue3 intermediate guide - composition API
  53. Analysis of URL
  54. These 10 widgets that every developer must know
  55. Spring's nohttp Manifesto: eliminate http://
  56. Learn more about JS prototypes
  57. Refer to await to JS to write an await error handling
  58. A short article will directly let you understand what the event loop mechanism is
  59. Vue3 uses mitt for component communication
  60. Characteristics and thinking of ES6 symbol