directory

  • What is Tabable?
  • Tabable usage
  • Advanced the
  • Other methods of Tabable
  • Webpack process
  • conclusion
  • Actual combat! Write a plug-in

Webpack can be understood as an event-based programming paradigm, a collection of plug-ins.

Running these plug-ins on the Webapck event stream is the base class Tapable written by WebPack itself.

Tapable exposes the way to mount the plugin, allowing us to control the plugin to run on the Webapack event stream (figure below). As we will see later, the core object Compiler, Compilation, and so on are inherited from the Tabable class. (As shown below)

What is Tabable?

The Tapable library exposes a number of Hook classes that provide hooks for plug-ins to mount.

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");
Copy the code

Tabable usage

  • 1. Create a new Hook
    • Tapable exposes all the class methods, new a class method to get the hook we need.
    • Class takes an array parameter, options, which is not mandatory. Class methods take the same number of arguments, depending on the arguments passed.
const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);
Copy the code
  • 2. Bind the hook using tap/tapAsync/tapPromise

Tabpack provides methods for binding hooks synchronously and asynchronously, and both have methods for binding events and executing events.

Async* Sync*
Binding: tapAsync/tapPromise/tap Binding: tap
Execution: callAsync/promise Implementation: the call
  • 3. Call /callAsync executes the binding event
const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

//绑定事件到webapck事件流
hook1.tap('hook1', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3)) //1,2,3

//执行绑定的事件
hook1.call(1,2,3)
Copy the code

  • Take a chestnut
    • Define a Car method to create a new hook on internal hooks. , respectively,Synchronous hooksAccelerate, break (accelerate accepts one parameter),Asynchronous hookscalculateRoutes
    • Use the hook correspondingBind and execute methods
    • CalculateRoutes usetapPromiseYou can return apromiseObject.
// tapable const {SyncHook, AsyncParallelHook} = require('tapable'); Constructor () {this.hooks = {accelerate: new SyncHook(["newSpeed"]), break: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"]) }; } } const myCar = new Car(); Tap ("WarningLampPlugin", () => console.log('WarningLampPlugin')); / / hooks binding synchronization And mass participation myCar. Hooks. Accelerate. Tap (" LoggerPlugin newSpeed = > console. The log (` Accelerating to ${newSpeed} `)); MyCar. / / bind an asynchronous Promise hook hooks. CalculateRoutes. TapPromise (" calculateRoutes tapPromise ", (source, target, routesList, callback) => { // return a promise return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log(`tapPromise to ${source}${target}${routesList}`) resolve(); }, 1000)})}); // Execute synchronous hook mycar.links.break.call (); myCar.hooks.accelerate.call('hello'); console.time('cost'); / / execute asynchronous hook myCar. Hooks. CalculateRoutes. Promise (' I ', 'love', 'tapable). Then (() = > {console. TimeEnd (' cost'); }, err => { console.error(err); console.timeEnd('cost'); })Copy the code

The results

WarningLampPlugin
Accelerating to hello
tapPromise to ilovetapable
cost: 1003.898ms
Copy the code

CalculateRoutes can also use tapAsync binding hooks, note that the asynchronous callback is terminated with callback.

myCar.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => { // return a promise setTimeout(() => { console.log(`tapAsync to ${source}${target}${routesList}`) callback(); }, 2000)}); myCar.hooks.calculateRoutes.callAsync('i', 'like', 'tapable', err => { console.timeEnd('cost'); if(err) console.log(err) })Copy the code

The results

WarningLampPlugin
Accelerating to hello
tapAsync to iliketapable
cost: 2007.850ms
Copy the code

Step it up

You’ve probably learned to use Tapable by now, but how does it relate to the Webapck/Webpack plug-in?

We changed the code slightly and split it into two files: Compiler.js and myplugin.js

Compiler.js

  • Change the Class Car Class name to the core of WebpackCompiler
  • Accept the plugins passed in options
  • Pass the Compiler as a parameter to the plugin
  • Execute the run function, which triggers the execution of the corresponding hook function at each stage of compilation.
const { SyncHook, AsyncParallelHook } = require('tapable'); class Compiler { constructor(options) { this.hooks = { accelerate: new SyncHook(["newSpeed"]), break: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"]) }; let plugins = options.plugins; if (plugins && plugins.length > 0) { plugins.forEach(plugin => plugin.apply(this)); } } run(){ console.time('cost'); this.accelerate('hello') this.break() this.calculateRoutes('i', 'like', 'tapable') } accelerate(param){ this.hooks.accelerate.call(param); } break(){ this.hooks.break.call(); } calculateRoutes(){ const args = Array.from(arguments) this.hooks.calculateRoutes.callAsync(... args, err => { console.timeEnd('cost'); if (err) console.log(err) }); } } module.exports = CompilerCopy the code

MyPlugin.js

  • The introduction of the Compiler
  • Define your own plug-in.
  • The apply method accepts the Compiler parameter.

The Webpack plug-in is a JavaScript object with the Apply method. The Apply attribute is invoked by the Webpack Compiler, and the Compiler object is accessible throughout the compile life cycle.

  • Bind methods to the hooks on the Compiler.
  • Following the Webpack rules,Pass a new instance to the plugins property.
Const Compiler = require('./Compiler') class MyPlugin{constructor() {} apply(conpiler){// Accept Compiler arguments conpiler.hooks.break.tap("WarningLampPlugin", () => console.log('WarningLampPlugin')); conpiler.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`)); conpiler.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => { setTimeout(() => { console.log(`tapAsync to ${source}${target}${routesList}`) callback(); }, 2000)}); } // Pass a new instance to the plugins property const myPlugin = new myPlugin (); // Pass a new instance to the plugins property const myPlugin = new myPlugin (); const options = { plugins: [myPlugin] } let compiler = new Compiler(options) compiler.run()Copy the code

The results

Accelerating to hello
WarningLampPlugin
tapAsync to iliketapable
cost: 2015.866ms
Copy the code

After transformation, it runs normally, and the logic of the plug-in is gradually straightened out in accordance with the thought of Compiler and Webpack plug-in.

Other methods of Tabable

type function
Hook Suffix for all hooks
Waterfall Synchronous method, but it passes the value to the next function
Bail Fusing: When the function has any return value, the current execution of the function stops
Loop Listeners return true to continue the loop and undefine to end the loop
Sync Synchronized methods
AsyncSeries Asynchronous serial hook
AsyncParallel Asynchronous parallel execution hooks

We can choose the appropriate synchronous/asynchronous hooks based on our development needs.

Webpack process

From the above reading, we know how to mount hooks on the Webapck event stream.

Suppose we now want to customize a plug-in that changes the contents of the final output resource, on which hook should we add the event? Which step can get the webPack compiled resources to modify?

So the next task is to understand the webpack process.

Post a classic Webpack flow chart shared by Taobao team, and then slowly analyze ~

1. The webpack entrance(webpack. Config. Js + shell options)

Read and merge parameters from configuration file package.json and Shell statements to get the final parameters;

Every time you type webpack on the command line, the operating system invokes the./node_modules/. Bin /webpack shell script. This script will go call. / node_modules/webpack/bin/webpack. Js and additional input parameters, such as – p – w.

2. Use YARgs parameter parsing(optimist)

yargs.parse(process.argv.slice(2), (err, argv, output) => {})
Copy the code

The source address

3. Webpack initialization

(1) Build compiler objects

let compiler = new Webpack(options)
Copy the code

The source address

(2) Register NOdeEnvironmentPlugin

new NodeEnvironmentPlugin().apply(compiler);
Copy the code

The source address

(3) Basic plug-in hanging in options, call the WebpackOptionsApply library to initialize the basic plug-in.

if (options.plugins && Array.isArray(options.plugins)) {
	for (const plugin of options.plugins) {
		if (typeof plugin === "function") {
			plugin.apply(compiler);
		} else {
			plugin.apply(compiler);
		}
	}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.options = new WebpackOptionsApply().process(options, compiler);
Copy the code

The source address

4. runBegin to compile

if (firstOptions.watch || options.watch) { const watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {}; if (watchOptions.stdin) { process.stdin.on("end", function(_) { process.exit(); // eslint-disable-line }); process.stdin.resume(); } compiler.watch(watchOptions, compilerCallback); if (outputOptions.infoVerbosity ! == "none") console.log("\nwebpack is watching the files... \n"); } else compiler.run(compilerCallback);Copy the code

There are two cases:

1) Watching: Monitor file changes

2) Run: executes compilation

The source address

5. The triggercompile

BeforeRun ->run->beforeCompile->compile->make->seal beforeRun->run->beforeCompile->compile->make->seal

(2) Built key Compilation objects

In the run() method, this.compile () is executed.

This.compile () created the compilation

this.hooks.beforeRun.callAsync(this, err => { ... this.hooks.run.callAsync(this, err => { ... this.readRecords(err => { ... this.compile(onCompiled); }); }); }); . compile(callback) { const params = this.newCompilationParams(); this.hooks.beforeCompile.callAsync(params, err => { ... this.hooks.compile.call(params); const compilation = this.newCompilation(params); this.hooks.make.callAsync(compilation, err => { ... compilation.finish(); compilation.seal(err => { ... this.hooks.afterCompile.callAsync(compilation, err ... return callback(null, compilation); }); }); }); }); }Copy the code

The source address

const compilation = this.newCompilation(params);
Copy the code

The Compilation process is responsible for the entire Compilation process and includes methods for each part of the build. A reference to compiler is retained inside the object.

When Webpack is running in development mode, a new Compilation is created whenever a file change is detected.

Compilation is important. It compiles production resource transformation files.

6.addEntry() Make parses entry files to create module objects

The make event is triggered in Compile and addEntry is called

In the Make hook of Webpack, tapAsync registers a DllEntryPlugin that calls the compilation of entry modules.

This registration is executed in the compiler.compile () method.

The addEntry method adds all entry modules to the build build queue, starting the build process.

DllEntryPlugin.js

compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
	compilation.addEntry(
		this.context,
		new DllEntryDependency(
			this.entries.map((e, idx) => {
				const dep = new SingleEntryDependency(e);
				dep.loc = {
					name: this.name,
					index: idx
				};
				return dep;
			}),
			this.name
		),
		this.name,
		callback
	);
});
Copy the code

The source address

I was surprised to see how the process ended up in dllentryplugin.js after compiling in compiler.js.

Said it will be before WebpackOptionsApply. The process () to initialize the plug-in, execute the compiler. The hooks. EntryOption. Call (options. The context, the options. Entry);

WebpackOptionsApply.js

class WebpackOptionsApply extends OptionsApply { process(options, compiler) { ... compiler.hooks.entryOption.call(options.context, options.entry); }}Copy the code

process

entryOption

DllPlugin.js

compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => { const itemToPlugin = (item, name) => { if (Array.isArray(item)) { return new DllEntryPlugin(context, item, name); } throw new Error("DllPlugin: supply an Array as entry"); }; if (typeof entry === "object" && ! Array.isArray(entry)) { Object.keys(entry).forEach(name => { itemToPlugin(entry[name], name).apply(compiler); }); } else { itemToPlugin(entry, "main").apply(compiler); } return true; });Copy the code

DllPlugin

Actually addEntry method, there are a lot of entrance, SingleEntryPlugin also registered a compiler. The hooks. Make. TapAsync hook. Main again WebpackOptionsApply here. The process () process (233).

There are a lot of entrance, interested can debug the sequence ~

7. Building blocks

Executing the _addModuleChain() method in compilation.addentry does two main things. One is to obtain the corresponding module factory according to the type of the module and create the module, the other is to build the module.

Create a module (NormalModule, MultiModule, ContextModule, DelegatedModule, etc.) using the * moduleFactory. create method to load the loader used by the module. Acorn is called to parse the source file processed by loader to generate the abstract syntax tree AST. Iterate through the AST to build the modules that the module depends on

addEntry(context, entry, name, callback) { const slot = { name: name, request: entry.request, module: null }; this._preparedEntrypoints.push(slot); this._addModuleChain( context, entry, module => { this.entries.push(module); }, (err, module) => { if (err) { return callback(err); } if (module) { slot.module = module; } else { const idx = this._preparedEntrypoints.indexOf(slot); this._preparedEntrypoints.splice(idx, 1); } return callback(null, module); }); }Copy the code

AddEntry addModuleChain() source address

8. Encapsulate build results (SEAL)

Webpack will listen to seal event to call each plug-in to encapsulate the built result. It will sort out each module and chunk successively, generate compiled source code, merge, split, and generate hash. It is also a key part of our code optimization and feature addition during development.

template.getRenderMainfest.render()
Copy the code

Chunk is formatted for _webpack_requie() using MainTemplate, ChunkTemplate.

9. Output resources (emit)

Output Assets to the path of Output.

conclusion

Webpack is a collection of plug-ins that tapable controls to run on the WebPack event stream. The compilation module and encapsulation of compilation are mainly relied on.

In fact, the entry file of Webpack instantiates Compiler and invokes the run method to open the compilation. The main compilation of Webpack is executed in the following sequence of hook calls.

  • Compiler:beforeRun clears the cache
  • Compiler: Run registers the cache data hooks
  • Compiler:beforeCompile
  • Compiler:compile Starts to compile
  • Compiler: Make analyzes dependencies and indirectly dependent modules from the entry and creates module objects
  • Compilation:buildModule building
  • Compiler: normalModuleFactory build
  • Compilation:seal Build result encapsulation, cannot be changed
  • Compiler:afterCompile completes the build and caches the data
  • Compiler: Emit output to the dist directory

A Compilation object contains the current module resources, compile-build resources, changing files, and so on.

The Compilation object also provides many event callbacks for plug-ins to extend.

The important part of the Compilation is assets. If we want to generate files for you with webpack, we have to add the corresponding file information to assets.

Compilation.getstats () gets the production files and some information about chunkhash. , etc.

Actual combat! Write a plug-in

This time I’ll try to write a simple plug-in that will help us remove unwanted comments from the bundle.js generated by the WebPack package

How to write a plug-in?

Refer to the official Webpack tutorial Writing a Plugin

A WebPack Plugin consists of the following steps:

  1. A JavaScript class function.
  2. Define an injection in the function prototypecompilerThe object’sapplyMethods.
  3. applyThe compiler inserts the specified event hooks into the compiler function and retrieves the compilation object in the hook callback
  4. Modify webapack internal instance data using the Compilation manipulation.
  5. Asynchronous plug-in that uses callback after data processing

Complete the initial plug-in architecture

Before Tapable, I wrote a MyPlugin class function that satisfies the first two points of the Webpack plugin structure (a JavaScript class function with an injection compiler defined in the function prototype).

Now we need Myplugin to satisfy the last three points. First, use the event hooks specified by the Compiler.

class MyPlugin{ constructor() { } apply(conpiler){ conpiler.hooks.break.tap("WarningLampPlugin", () => console.log('WarningLampPlugin')); conpiler.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`)); conpiler.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => { setTimeout(() => { console.log(`tapAsync to ${source}${target}${routesList}`) callback(); }, 2000)}); }}Copy the code

Common objects for plug-ins

object hook
Compiler run,compile,compilation,make,emit,done
Compilation buildModule,normalModuleLoader,succeedModule,finishModules,seal,optimize,after-seal
Module Factory beforeResolver,afterResolver,module,parser
Module
Parser program,statement,call,expression
Template hash,bootstrap,localVars,render

Writing a plug-in

class MyPlugin { constructor(options) { this.options = options this.externalModules = {} } apply(compiler) { var reg = / (([^ \ \ \ ""] * (\ \.) ?). * ") | (' ([^ \ \ \ '] * (\ \.) ?). * ') | (\ / {2}. *? (\r|\n))|(\/\*(\n|.) *? \*\/)|(\/\*\*\*\*\*\*\/)/g compiler.hooks.emit.tap('CodeBeautify', (compilation)=> { Object.keys(compilation.assets).forEach((data)=> { let content = compilation.assets[data].source() // To handle the text content = content. the replace (reg, function (word) {/ / remove annotation text after return / ^ \ {2} / test (word) | | / ^ \ \ *! /.test(word) || /^\/\*{3,}\//.test(word) ? "" : word; }); compilation.assets[data] = { source(){ return content }, size(){ return content.length } } }) }) } } module.exports = MyPluginCopy the code

The first step is to use the Compiler’s EMIT hook

The emit event is emitted by firing the compiled code into the specified stream. When this hook executes, we can retrieve the compiled stream from the compilation object returned by the callback function.

compiler.hooks.emit.tap('xxx',(compilation)=>{})
Copy the code

The second step is to access the Compilation object. We use the binding to provide the emit hook function that compiles the Compilation reference. Each compilation will fetch a new compilation object. These compilation objects provide hook functions that hook into many steps of the build process.

The compilation returns a number of internal objects, as shown in partial screenshots below:

One of the things we need is compilation. Assets

assetsCompilation {
  assets:
   { 'js/index/main.js':
      CachedSource {
        _source: [Object],
        _cachedSource: undefined,
        _cachedSize: undefined,
        _cachedMaps: {} } },
  errors: [],
  warnings: [],
  children: [],
  dependencyFactories:
   ArrayMap {
     keys:
      [ [Object],
        [Function: MultiEntryDependency],
        [Function: SingleEntryDependency],
        [Function: LoaderDependency],
        [Object],
        [Function: ContextElementDependency],
     values:
      [ NullFactory {},
        [Object],
        NullFactory {} ] },
  dependencyTemplates:
   ArrayMap {
     keys:
      [ [Object],
        [Object],
        [Object] ],
     values:
      [ ConstDependencyTemplate {},
        RequireIncludeDependencyTemplate {},
        NullDependencyTemplate {},
        RequireEnsureDependencyTemplate {},
        ModuleDependencyTemplateAsRequireId {},
        AMDRequireDependencyTemplate {},
        ModuleDependencyTemplateAsRequireId {},
        AMDRequireArrayDependencyTemplate {},
        ContextDependencyTemplateAsRequireCall {},
        AMDRequireDependencyTemplate {},
        LocalModuleDependencyTemplate {},
        ModuleDependencyTemplateAsId {},
        ContextDependencyTemplateAsRequireCall {},
        ModuleDependencyTemplateAsId {},
        ContextDependencyTemplateAsId {},
        RequireResolveHeaderDependencyTemplate {},
        RequireHeaderDependencyTemplate {} ] },
  fileTimestamps: {},
  contextTimestamps: {},
  name: undefined,
  _currentPluginApply: undefined,
  fullHash: 'f4030c2aeb811dd6c345ea11a92f4f57',
  hash: 'f4030c2aeb811dd6c345',
  fileDependencies: [ '/Users/mac/web/src/js/index/main.js' ],
  contextDependencies: [],
  missingDependencies: [] }
Copy the code

Optimize all chunk resources (assets). Assets are stored as key-values in compilation.assets.

Step 3, walk through assets.

1) The key in assets array is the name of the resource. In Myplugin, we get object.key ()

main.css
bundle.js
index.html
Copy the code

2) Call object.source () to get the contents of the resource

compilation.assets[data].source() 
Copy the code

3) Use re to remove comments

 Object.keys(compilation.assets).forEach((data)=> {
    let content = compilation.assets[data].source() 
    content = content.replace(reg, function (word) { 
        return /^\/{2,}/.test(word) || /^\/\*!/.test(word) || /^\/\*{3,}\//.test(word) ? "" : word;
    })
});
Copy the code

Step 4, update the compilation.assets[data] object

compilation.assets[data] = {
    source(){
        return content
    },
    size(){
        return content.length
    }
}
Copy the code

Step 5 Reference the plug-in in the Webpack

webpack.config.js

const path  = require('path')
const MyPlugin = require('./plugins/MyPlugin')

module.exports = {
    entry:'./src/index.js',
    output:{
        path:path.resolve('dist'),
        filename:'bundle.js'
    },
    plugins:[
        ...
        new MyPlugin()
    ]
}
Copy the code

Plug-in address

The resources

  • Taobaofed.org/blog/2016/0…
  • Github.com/webpack/tap…
  • Zoumiaojiang.com/article/wha…
  • Webpack.js.org/api/plugins…
  • Webpack.js.org/contribute/…
  • Webpack.docschina.org/api/plugins…

Recruitment is stuck

Bytedance is hiring!

Position Description: Front-end Development (senior) ToB Direction — Video Cloud (Base: Shanghai, Beijing)

1. Responsible for the productization of multimedia services such as voD/live broadcast/real-time communication and the construction of business cloud platform;

2. Responsible for the construction and system development of multimedia quality system, operation and maintenance system;

3, good at abstract design, engineering thinking, focus on interaction, to create the ultimate user experience.

Job requirements

1. Major in Computer, communication and electronic information science is preferred;

2, familiar with all kinds of front end technology, including HTML/CSS/JavaScript/Node. Js, etc.

3. In-depth knowledge of JavaScript language and use of React or vue.js and other mainstream development frameworks;

4. Familiar with Node.js, Express/KOA and other frameworks, experience in developing large server programs is preferred;

5. Have some understanding of user experience, interactive operation and user demand analysis, experience in product or interface design is preferred;

6, own technical products, open source works or active open source community contributors are preferred.

Position window

Relying on the audio and video technology accumulation and basic resources of douyin, Watermelon video and other products, the video Cloud team provides customers with the ultimate one-stop audio and video multimedia services, including audio and video on demand, live broadcasting, real-time communication, picture processing, etc. Internally as the video technology center, service internal business; External to create productized audio and video multimedia service solutions, serving enterprise users.

The team has standardized project iteration process and perfect project role configuration; Strong technical atmosphere, embrace open source community, regular sharing, so that everyone can grow with the rapid business, with technology to change the world!

The delivery way

You can send your resume directly to: [email protected]

You can also scan the push two-dimensional code online delivery, looking forward to your participation! ~