Introduction to the

The previous article explained how to understand the Tapable hook mechanism because it is the heart and soul of the WebPack program. While the hook mechanic is very flexible, it becomes an obstacle in the way of reading WebPack. Whenever Webpack is running, I have a Buddhist mentality, praying that there will be no problems in the middle, otherwise I will have to find problems for a long time, better not to pack. Especially the running mechanism of loader and plugin, when are these two triggered, which link of Webpack? These are all questions that require familiarity with webPack source code to answer.

Follow me step by step to uncover the mystery of Webpack.

How do I debug WebPack

This section describes how to debug WebPack. If you have your own debug method, or a more mainstream one, leave a comment.

The easy Version of Webpack starts

To do a good job, he must sharpen his tools. I believe that when you first learn Webpack, you must follow the official documentation to run webpack website.

Webpack start documentation, -> MOE new directions

The initial operation should rely on webpack-CLI by typing NPX webpack –config webpack.config.js in the little black box and then enter. While Webpack-CLI will help us take into account most of the problems that arise during the packaging process, it will make the source code for WebPack even stranger, as if configuration is everything.

In these awkward times, we have to take a different approach to development than the official entry method.

I wrote a simple debug code to start webpack, as follows:

// let webpack=require('webpack'); // specify webpack configuration file let config=require("./webpack.config.js"); Let compile=webpack(config); Compile. Run ();Copy the code

If you want to know where my inspiration for this code came from? I’ll tell you from Webpack-CLI.

Pick out the key running parts and reorganize to make a simple Webpack launch.

Talkative Writer: Why would I do that? The less code we have, the easier it is to analyze, and the more “irrelevant” code we have, the more code we can’t see. Of course, wait until this part of the master, and then look at the CLI code, perhaps the harvest will be greater.

Warm reminder of configuration

Although we all configure Entry, we may ignore the configuration of the Context. If our CMD is in the current directory, then the execution is OK, but if we are not in the current directory and then execute, there is a good chance that the path will have problems. In order to prevent the tragedy of hiding, I recommend the on-machine context configuration, which is context: the absolute path to your current project.

module.exports = {
  //...
  context: path.resolve(__dirname, 'app')};Copy the code

Break up! debugger

The key part comes, write a simple webpack is mainly to facilitate the interruption point! Increase the readability of the program.

Non-vs Code player entry

If you’re a termial and Chrome lover, here’s how to do it! Click on the reference document for details on how to do this.

node --inspect-brk debugger1.js
Copy the code

Then we can have fun playing with the friendly Chrome as we would debug a web page. Although every step is displayed very well, I don’t want to know the native node methods such as fs reading, timer running and module loading. I clicked several hundred times for Next, but the main webpack process did not take a few steps, which greatly challenged my patience. If there are friends who step by step to the last step, I hope you can come and share with us. To avoid going into too much detail, at this point we can interrupt the point in the appropriate place:

options = new WebpackOptionsDefaulter().process(options); Compiler = new compiler (options.context);Copy the code

After the WebpackOptionsDefaulter runs, the program will automatically stop debugging.

Vscode players

If you are a vscode player, in addition to the debugger method above, you can also use red dots as breakpoints, which is more convenient. Finally, you can clear all breakpoints with one click.

At the same time, you can also enter the parameters you want to know in the debug console when the current breakpoint.

What is the main process of Webpack

I can explain the main process of Webpack in the following three ways:

Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler Compiler

Professional version: The process of Webpack is the process of Compiler control, Compilation professional parsing, ModuleFactory module generation, Parser source code parsing, and finally Template module combination and output package files.

Rough version: Webpack is the process of breaking up source code and reassembling it.

The source code interpretation

Let’s get straight to understanding WebPack from the professional version. From the startup code above we can see that Webpack (config) is the key code to start the WebPack package, that is, webpack.js is our first research object.

Because the author of various debugging Webpack, various breakpoints, resulting in the number of lines of the source code and the number of lines is inconsistent, so here I will directly throw the code rather than the number of lines, we compare the source code to webpack.

The source of everything webpack.js

Do you think I’m going to start with the first introduction? It doesn’t exist. Let’s start with the key logic.

options = new WebpackOptionsDefaulter().process(options); compiler = new Compiler(options.context); compiler.options = options; new NodeEnvironmentPlugin().apply(compiler); , omit the custom plug-in binding compiler. The hooks. The environment. The call (); compiler.hooks.afterEnvironment.call(); compiler.options = new WebpackOptionsApply().process(options, compiler);Copy the code

If you don’t know what I’m talking about, don’t panic, let’s go through the lines, every line here is important.

options = new WebpackOptionsDefaulter().process(options); The keyword of this line is Default. From the keyword, we can guess that the purpose of this class is to override the Default configuration of webpack by overriding the custom part of webpack.config.js.

Pick a line from this class so you can understand it.

this.set("entry", "./src");
Copy the code

This is the default configuration of the entry. If we don’t have the entry, the application will automatically find the file under SRC to pack.

One of the great features of Webpack 4.0 is that it has zero configuration. We can pack it without webpack.config.js. Why is that? Does WebPack really need no configuration? Artificial intelligence? No! Because there are default configurations, just like all programs have default configurations for initialization.

New Compiler(options.context), the very important Compiler that basically compiles the process from this class. The options. The context of this value is the absolute path to the current folder, through WebpackOptionsDefaulter. Js code snippet of code snippets of the default configuration can not only understand. This class will be examined later.

this.set("context", process.cwd());
Copy the code

This is followed by a series of configuration for the Compiler, which includes hooks for NodeEnvironmentPlugin and custom plugins, which trigger some hooks for the environment. It’s like starting your car before driving. For example, when parsing a file (resolver), we will definitely use the file system, and how to read the file. This is to mount the inputFileSystem into the file system in the Compiler, and then use the Compiler to control which plug-ins need this functionality and send it to it.

class NodeEnvironmentPlugin {
	apply(compiler) {
		compiler.inputFileSystem = new CachedInputFileSystem(
			new NodeJsInputFileSystem(),
			60000
		);
		//....
		compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
			if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
		});
	}
}
module.exports = NodeEnvironmentPlugin;

Copy the code

compiler.options = new WebpackOptionsApply().process(options, compiler); Options is also processed here. If the first step is to format the configuration, then the configuration is activated in compiler. This class is important because many hooks are activated in the Compiler and tap functions on some of them.

Key configuration options activation parsing:

  • This is a parser for parse. If the file is JS, the parse will be used, which means that this is done on loader.

    new JavascriptModulesPlugin().apply(compiler);
    Copy the code
  • This line is used for parsing, i.e. entry parsing, SingleEntryPlugin or MultiEntryPlugin. This method is equivalent to the entry program already in place, ready to run on subsequent command.

    new EntryOptionPlugin().apply(compiler);
    compiler.hooks.entryOption.call(options.context, options.entry);
    Copy the code
  • The hook that executes when the plug-in hooks are all hung.

    compiler.hooks.afterPlugins.call(compiler);
    Copy the code
  • Then there are various kinds of path resolution hooks, according to our custom resolver to resolve.

    compiler.resolverFactory.hooks.resolveOptions
    Copy the code

The key breakthrough is compiler.js

It can be said that the Compiler. Js class really controls the packaging process of Webpack. If what Webpack. js does is to prepare, then Compiler is to roll up its sleeves.

constructor

We parse Compiler from constructor.

Compiler first defines a bunch of hooks. If you look carefully, you will find that this is each stage of the process (the code here is very readable), that is, each stage has a hook, what does this mean? We can use these hooks to attach our plugins, so Compiler is important.

The key hook Hook type Hook parameters role
beforeRun AsyncSeriesHook Compiler Preparation activities before running, mainly enable the function of reading files.
run AsyncSeriesHook Compiler The “machine” is already running, and caching is enabled before compilation to improve efficiency.
beforeCompile AsyncSeriesHook params To prepare for Compilation, create the ModuleFactory, create the Compilation, and bind the ModuleFactory to the Compilation. It also handles modules that don’t need to be compiled, such as ExternalModule (remote module) and DllModule (third-party module).
compile SyncHook params Compile the
make AsyncParallelHook compilation Start building modules from the addEntry function of the Compilation
afterCompile AsyncSeriesHook compilation We’re done compiling
shouldEmit SyncBailHook compilation Get a telegram from the compilation to determine whether the compilation was successful and whether output can begin.
emit AsyncSeriesHook compilation Output file
afterEmit AsyncSeriesHook compilation The output is
done AsyncSeriesHook Stats Success or failure, the dust has settled.

Compiler.run()

From the name of the function, we can roughly guess its function, but it is from the running process of Compiler to deepen the understanding of Compiler. The Compiler. The run () to run away!

The beforeRun async hook is triggered to bind the object that reads the file. This is followed by the Run async hook, which processes cached modules, reducing compiled modules, and speeding up compilation. Then it will go to compiler.compile ().

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

When compiler.compile is finished, the onCompiled function in run will be called back. This function generates the compiled content into a file. We can see that shouldEmit first checks if the compilation is successful, and if it isn’t, it finishes done and prints the corresponding message. On success, compiler.emitAssets is called to package the file.

if (this.hooks.shouldEmit.call(compilation) === false) {
	...
	this.hooks.done.callAsync(stats, err => {
		...
    }
    return
	
}
this.emitAssets(compilation, err => {
    ...
    if (compilation.hooks.needAdditionalPass.call()) {
    ...
	    this.hooks.done.callAsync(stats, err => {});
    };
})

Copy the code

Compiler.compile()

The previous section only discussed the flow of the compiler. run method, not compiler.compile, which is what the name implies. So what happens during compilation?

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(); ompilation.seal(err => { ... this.hooks.afterCompile.callAsync(compilation, err => { ... This is the callback function, which is mainly used to print the compiled code... }); }); }); });Copy the code

The normalModuleFactory is one of the most common of these. This factory is passed into the hook for subsequent plugins or hooks to manipulate the module.

If the hook wants to communicate with the program, such as adding content to the Compiler, it needs to be passed into the hook. Otherwise, there is no interface exposed to the plug-in.

BeforeCompile prepares, and then starts the compile hook.

There’s a new Compilation, a very important class that focuses on Compilation.

Make this hook is officially compiled, so completion of this hook means compilation is complete and seal can be wrapped. So what steps are performed when the make hook fires?

Remember the EntryOptionPlugin mentioned in webpack.js?

    new EntryOptionPlugin().apply(compiler);
    compiler.hooks.entryOption.call(options.context, options.entry);
Copy the code

The module construction of Webpack is actually analyzed and built through entry, which is the entry file. This means that an entry file triggers compliation. addEntry once, and then the Compilation starts building modules.

EntryOptionPlugin is a plug-in that helps us deal with entry types. It uses different configurations of entry in webpack.config.js to help us match different entryPlugins. There are three types of entry, SingleEntryPlugin, MultiEntryPlugin, and DynamicEntryPlugin, which can be easily distinguished by name. Typically, a Compiler will only trigger an EntryPlugin, and within this EntryPlugin there will be an entry to our building module, the compilation.

compiler.hooks.make.tapAsync("SingleEntryPlugin|MultiEntryPlugin|DynamicEntryPlugin",(compilation, callback) => { ... compilation.addEntry(context, dep, name, callback); . });Copy the code

In addition to helping us open up the programmer operation, EntryPlugin also binds an event to the module factory type of the current entry.

compiler.hooks.compilation.tap("SingleEntryPlugin",(compilation, { normalModuleFactory }) => {
	compilation.dependencyFactories.set(
		SingleEntryDependency,
		normalModuleFactory
	);
});
Copy the code

This hook function defines the module type of SingleEntry so that later compliation compilations will use normalModuleFactory to create modules.

The make hook acts as a turning point from the main process to the compilation process, compilation, a class that focuses on compiler optimization.

After the compilation was successful, we returned to the compiler and put the emitAssest content into the hard disk.

Professional Compilation for 100 years – compiler.js

If Compiler is a process, Compilation is the home of Compilation. That is, after he processed the source code to get sublimated into the appearance of the rules.

The Compilation is summed up in adding entry, analyzing modules through entry, and analyzing dependencies between modules, just like diagrams. After the build, seal Compilation encapsulates this stage. This Compilation does a set of optimizations and turns the parsed modules into standard Webpack modules and outputs them for later use. This is provided that you hook the optimizer plugin to hooks that trigger the optimizer. However, the hook also has to register a function to take effect.

Okay we get the information from Compile to analyze compiler.js in order of appearance

AddEntry — where it all begins

The SingleEntryPlugin (and others) mentioned in the previous section is a startup port that by the time compile.hooks. Make is triggered, The compilation.addEntry method in SingleEntryPlugin is launched, which starts building the entry module and adds it to the program when it succeeds.

// Context,entry,name are all options values. addEntry(context, entry, name, callback) { this._addModuleChain(context,entry,module => { this.entries.push(module); },(err, module) => { ...if (module) {
				slot.module = module;
			} else {
				const idx = this._preparedEntrypoints.indexOf(slot);
				if(idx >= 0) { this._preparedEntrypoints.splice(idx, 1); }}...returncallback(null, module); }); }Copy the code

Add module dependencies_addModuleChain

This method is the main method for module building and is called by addEntry and returned when the module is completed.

  • _addModuleChain, building modules, while preserving dependencies between modules.
    • const moduleFactory = this.dependencyFactories.get(Dep); moduleFactory.create(...)Here,moduleFactoryCreate is the creation factory of the current module type, from which new products (new modules) are created.
      • this.addModule(module)->this.modules.push(module);, add the module to compiler.modules.
      • onModule(module);, this method calls addEntrythis.entries.push(module)That is, add the entry module to compiler.entries.
      • this.buildModule->this.hooks.buildModule.call(module); module.build(...)Which provides hooks to operate on the Module. You can define your own plugin to operate on them. This is followed by the creation of a module itself, depending on the module type, such as the normalModuleFactor module created from the NormalModule class.
        • _addModuleChain built-in methodafterBuild()The method is to get the time taken to create the module and its dependencies, and then execute the callback if there is one.

After the build, go back to Compiler and finish our build

Finish does two things here, one is to start the hooks that end the build, and then it collects the problems that each module builds.

With everything in place, start encapsulating SEAL (callback)

The product is ready and ready to be packaged for export.

Start executing optimized hooks one by one, if you have optimized hooks written.

Start optimizing:

Here are the hooks for tuning dependencies

Here is the hook for optimizing the Module

Here are the hooks for optimizing Chunk

.

Too many optimizations. The writer’s already gone.

After optimization, the callback function from the Compiler is executed, which generates the file.

In addition to the various hook calls, Seal does an important job of reaggregating the formatted JS through the Template Template and then calling back into the Compiler generation file. This piece will be analyzed in detail in the Template later.

In fact, Compiler and Compliation are the main process, and these two classes cooperate with each other. Then there are a few more critical classes that, from my perspective, are not part of the main flow but are important because they are the classes created by the module. Just like the products on the assembly line, the products themselves have nothing to do with the flow of the assembly line.

Module birthplace – moduleFactory

ModuleFactory is an instance of a module, but it’s not part of the main flow, just like lego parts. Without it, what would we do with it? One cannot make bricks without straw! The moduleFactory that needs to be compiled is divided into two types: context and normal. I have encountered almost all normal types, so I use norAML as the main class to explain the moduleFactory.

His mission

Since he is a factory, his mission is to make products. Here the module is the product, so the factory needs only one. Our factory was founded in running the Compiler.com, and passed as a parameter to the compile. Hooks. BeforeCompile and compile.hooks.com running the two hooks, which means that we wrote the two hooks mount function, We can call this factory to help us create the processing module.

const NormalModule = require("./NormalModule");
const RuleSet = require("./RuleSet");
Copy the code

These two parameters are important. One is the product itself, which means that instances created through NormalModule are modules. RuleSet is loaders. RuleSet includes built-in loaders and custom loaders. That is to say, Factory does two things: first, it matches the corresponding Parser, makes it a parser for the current module and parses the source code into AST mode. Second, it creates a generator to generate the code and restore the AST (which is used for template generation). The third thing is to create a module. During module construction, find the corresponding Loader, replace the source code, add the corresponding dependent module, and then provide the corresponding parser when module parsing, and provide the corresponding generator when template generation.

NormalModule class

Fatory provides raw materials (options) and tools (Parser), which is like passing parameters to an automated machine. This normalModule is the creative machine that builds the module and turns the source code into an AST syntax tree.

build(options, compilation, resolver, fs, callback) { //... return this.doBuild(options, compilation, resolver, fs, err => { //... this._cachedSources.clear(); / /... Try {const result = this.parser.parse(// focus here. / /...). ; / /... }); }Copy the code

After the Compilation of modules, the module build method is triggered to generate modules. The logic is very simple: enter the source file, and then use Reslover to parse the loader and the dependent files, and return the result. Then the loader converts this to a standard Webpack module and stores the source for use when the template is generated.

When it comes time to package, the compiled source code is reorganized into JS code, mainly through the Facotry generator for the module.

source(dependencyTemplates, runtimeTemplate, type = "javascript") { //... Const source = this.generator. Generate (this, dependencyTemplates, runtimeTemplate, type); / /... Return cachedSource; }Copy the code

Loader march

Where and how does loader execute

For starters, loader and plugin can be silly to confuse (yes, I am that fool). After digging into the source code, I clearly understand the difference between the two.

Ignorant I I know the drill
Difference 1: Plugin is broad. Well, it really means a lot Difference 1: Plugins can appear at any process node and Loaders have a specific scope of activity
Ruleset is not used for module.loaders, but for module.ruleset Difference 2: Plugin can do nothing related to source code, such as monitoring, loader can only parse source code into a standard module.

So where exactly does loader execute? Now that I know the Compilation, NormalModuleFactory, and NormalModule functions, let me explain how the Loader enters the Module.

Compilation._addModuleChain starts adding modules, triggering the compilation. buildModule method, and then calling normalModule. build to start creating modules. Create a module, invoked runLoaders to execute the loaders, but for the loader’s location, program or confused, so this time need to request NormalModuleFactory. ResolveRequestArray, Help us read the loader’s address, execute and return. Module by module, loader by loader, until the last module is created, and then it’s time for the compiler.seal process.

Soul Parser

Once the current module has finished processing loaders and converted the imported module into a standard JS module, it is time to decompress the source code into a standard AST syntax tree, relying on Parser. Parser is very powerful in that it helps us turn non-standard content into standard modules that are easy to package and do other things. The Parser works as a machine. Source files come in, process them, and output them. The source files don’t have any chemical effects on the Parser. Parser does not exist by the number of normalModules it is created with, but by module type. Imagine if you had a parser for every product in the factory, the efficiency would be successfully biubiUBIu reduced.

There are three types of javascript Parser: “Auto”, “Script”, and “module”. Factoy helps us match different types of Parser depending on the needs of the module.

normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", options => {
	return new Parser(options, "auto");
});
normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", options => {
	return new Parser(options, "script");
});
normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", options => {
	return new Parser(options, "module");
});
Copy the code

Does Parser actually parse our source code?

First, it becomes an AST — a standard syntax tree, structured code, easy to parse later. If the source is not an AST, it will be forced to be processed by the AST.

This parsing library, Webpack uses Acorn.

static parse(code, options) { ..... ast = acorn.parse(code, parserOptions); . return ast; } parse(source, initialState) { //... ast = Parser.parse(source, { sourceType: this.sourceType, onComment: comments }); / /... }Copy the code

Ding Dong – Your packaging Template Template

It’s finally time to wrap up, but this part is not easy.

The Template is triggered during compilation. Seal after the module has been built. We’re going to reassemble the modules that we’ve worked so hard to build into THE JS code that we saw in the bundle.

When we pack up js, we always use the same formula, right? Why is that? Clearly there is a standard template. Once our source files become AST, the processing of preparing the output depends on how the Template operation outputs and the webpack-source help us merge and replace modules that are still ast format. Finally, the output is based on the chunk merge.

There are five Template classes:

  • Template.js
  • MainTemplate.js
  • ModuleTemplate.js
  • RuntimeTemplate
  • ChunkTemplate.js

Of course! Template substitutions are done in Compilation, which is, after all, like a programmer, instructing people how to compile one by one.

Compilation. Seal triggered MainTemplate getRenderManifest, need to apply colours to a drawing of the information, then the hooks triggered by MainTemplate. Hooks. RenderManifest this hook, The corresponding function in the JavascriptModulePlugin is called to create a fileManifest containing packaging information to be returned for later use.

result.push({ render: () => compilation.mainTemplate.render( hash, chunk, moduleTemplates.javascript, dependencyTemplates ), filenameTemplate, pathOptions: { noChunkHash: ! useChunkHash, contentHashType: "javascript", chunk }, identifier: `chunk${chunk.id}`, hash: useChunkHash ? chunk.hash : fullHash });Copy the code

createChunkAssets(){ //... const manifest = template.getRenderManifest(...) // Get render list //... for (const fileManifest of manifest) { //... source = fileManifest.render(); / /... } / /... }Copy the code

After the prep work is done, it’s time to start rendering by calling the Render function of fileManifest, which is mainTemplate.render. Maintemplate. render triggers hooks. Render, which returns a ConcatSource resource. There are fixed templates, and there are called modules.

/ /... this.hooks.render.tap("MainTemplate",(bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => { const source = new ConcatSource(); source.add("/******/ (function(modules) { // webpackBootstrap\n"); / /... Source.add (this.links.modules. Call (// Get module resources new RawSource(""), chunk, hash, moduleTemplate, dependencyTemplates)); source.add(")"); return source; }); / /.. render(hash, chunk, moduleTemplate, dependencyTemplates) { //... let source = this.hooks.render.call( new OriginalSource( Template.prefix(buf, " \t") + "\n", "webpack/bootstrap" ), chunk, hash, moduleTemplate, dependencyTemplates ); / /... return new ConcatSource(source, ";" ); }Copy the code

Various modules of the Template to replace MainTemplate tasks assigned to the Template, let him to deal with the issue of the module, and call the Template. RenderChunkModules this method. This method starts by getting the replacement resources for all modules.

static renderChunkModules(chunk,filterFn,moduleTemplate,dependencyTemplates,prefix = ""){ const source = new ConcatSource(); const modules = chunk.getModules().filter(filterFn); / /... const allModules = modules.map(module => { return { id: module.id, source: moduleTemplate.render(module, dependencyTemplates, { chunk }) }; }); / /... / /... }Copy the code

The ModuleTemplate will then request the normalModule. source method. The Module uses the generator provided by the Factory to generate replacement code. During the Generate phase, the Module requests a RuntimeTemplate, which, as the name suggests, is used to replace run-time code.

source(dependencyTemplates, runtimeTemplate, type = "javascript") { //... const source = this.generator.generate( this, dependencyTemplates, runtimeTemplate, type ); const cachedSource = new CachedSource(source); / /.. return cachedSource; }Copy the code

It is then thrown into NormalModule to change this into a cachedSource and returned to ModuleTemplate for further processing. ModuleTemplate is used to package this module and should look like this:

We’ll go back to Template and continue processing, and after the ModuleTemplate processing, we’ll return something like this.

The revolution is not over yet! The replacement is still going on! We went back to the Template. RenderChunkModules, continue to replace.

static renderChunkModules(chunk,filterFn,moduleTemplate,dependencyTemplates,prefix = ""){
	const source= new ConcatSource(); const modules = chunk.getModules().filter(filterFn); / /... Returns if there are no modules"[]"
		source.add("[]");
		return source; / /... Const allModules = modules.map(//...) ; / /... Start adding module source.add("[\n"); / /... source.add(`/*${idx}* / `); source.add("\n");
		source.add(module.source);
		source.add("\n" + prefix + "]"); / /...return source;
}
Copy the code

We return the ConcatSource to maintemplate.render () and add a; , and then return to Compliation createChunkAssets.

This seal completes the template. As for the generated file, we use the webpack-source package to turn our arrays into strings, concatenate them, and output them.

All the pictures are from the author’s hand, welcome to reprint, please indicate the source. After more than a month of manipulation, I feel bald.

I’m working on my next paper. Feel loader needs more scraping. (laughs ~)