Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to add hooks to the require function of Node.js

2025-03-31 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

In this article Xiaobian for you to introduce in detail "Node.js 's require function how to add hooks", the content is detailed, the steps are clear, the details are handled properly, I hope this "Node.js require function how to add hooks" article can help you solve your doubts, the following follow the editor's ideas slowly in-depth, together to learn new knowledge.

Node.js is a JavaScript runtime environment based on the Chrome V8 engine. The early Node.js adopted the CommonJS module specification and officially supported the ES Modules feature from Node v13.2.0. It wasn't until v15.3.0 that the ES Modules feature stabilized and was compatible with the NPM ecosystem.

This article will introduce the workflow of the require function in Node.js, how to make Node.js execute the ts file directly and how to hijack the require function of Node.js correctly, so as to realize the function of hook. Next, let's introduce the require function.

Require function

Node.js applications are made up of modules, and each file is a module. For the CommonJS module specification, we import the module through the require function. So what happens inside the function when we use the require function to import the module? Here we take a look at the process of require through the call stack:

As you can see from the figure above, when using the require import module, the load method of the Module object is called to load the module. The implementation of this method is as follows:

/ / lib/internal/modules/cjs/loader.jsModule.prototype.load = function (filename) {this.filename = filename; this.paths = Module._nodeModulePaths (path.dirname (filename)); const extension = findLongestRegisteredExtension (filename); Module._ extensions [extension] (this, filename); this.loaded = true; / / omit some code}

Note: the version of the Node.js source code quoted in this article is v16.13.1

In the above code, the two important steps are:

Step 1: find the extension based on the file name

Step 2: find a matching loader in the Module._extensions object through the parsed extension.

Three different loaders are built into Node.js to load node, json, and js files. Node file loader

/ / lib/internal/modules/cjs/loader.jsModule._extensions ['.node'] = function (module, filename) {return process.dlopen (module, path.toNamespacedPath (filename));}

Json file loader

/ / lib/internal/modules/cjs/loader.jsModule._extensions ['.json'] = function (module, filename) {const content = fs.readFileSync (filename, 'utf8'); try {module.exports = JSONParse (stripBOM (content));} catch (err) {err.message = filename +':'+ err.message; throw err;}}

Js file loader

/ / lib/internal/modules/cjs/loader.jsModule._extensions ['.js'] = function (module, filename) {/ / If already analyzed the source, then it will be cached. Const cached = cjsParseCache.get (module); let content; if (cached?.source) {content = cached.source; cached.source = undefined;} else {content = fs.readFileSync (filename, 'utf8');} / omit part of the code module._compile (content, filename);}

Let's analyze the more important js file loader. By observing the above code, we can see that the core processing flow of the js loader can also be divided into two steps:

Step 1: load the contents of the js file using the fs.readFileSync method

Step 2: use the module._compile method to compile the loaded js code.

So what is the use of the above knowledge to us? In fact, after understanding the workflow of the require function, we can extend the Node.js loader. For example, let Node.js run the ts file.

/ register.jsconst fs = require ("fs"); const Module = require ("module"); const {transformSync} = require ("esbuild"); Module._extensions [".ts"] = function (module, filename) {const content = fs.readFileSync (filename, "utf8"); const {code} = transformSync (content, {sourcefile: filename, sourcemap: "both", loader: "ts", format: "cjs",}); module._compile (code, filename) }

In the above code, we introduced the built-in module module and then used the module's _ extensions object to register our custom ts loader.

In fact, the essence of the loader is a function, in which we use the transformSync API provided by the esbuild module to convert ts-> js code. When the code conversion is complete, the module._compile method is called to compile the code.

See here believe that some friends, but also think of the corresponding loader in Webpack, if you want to learn more, you can read more detailed explanation, one-time understand Webpack Loader this article.

Address: https://mp.weixin.qq.com/s/2v1uhw2j7yKsb1U5KE2qJA

Space is limited, the specific compilation process, we will not introduce it. Let's take a look at how to make a custom ts loader work. For Node.js to execute the ts code, we need to complete the registration of the custom ts loader before executing the ts code. Fortunately, Node.js provides us with a mechanism for preloading modules:

$node-- help | grep preload-r,-- require=... Module to preload (option can be repeated)

That is, with the-r,-require command line configuration item, we can preload the specified module. After learning about this, let's test the custom ts loader. First create an index.ts file and enter the following:

/ / index.tsconst add = (a: number, b: number) = > a + bscape console.log ("add (a, b) =", add (3,5))

Then enter the following command on the command line:

$node-r. / register.js index.ts

When the above command runs successfully, the console outputs the following:

Add (a, b) = 8

It is obvious that our custom ts file loader is working, and this extension mechanism is worth learning. In addition, it should be noted that in the load method, the findLongestRegisteredExtension function determines whether the file extension has been registered in the Module._extensions object, and if not, the .js string is returned by default.

/ / lib/internal/modules/cjs/loader.jsModule.prototype.load = function (filename) {this.filename = filename; this.paths = Module._nodeModulePaths (path.dirname (filename)); const extension = findLongestRegisteredExtension (filename); Module._ extensions [extension] (this, filename); this.loaded = true; / / omit some code}

This means that as long as the file contains valid js code, the require function can load it normally. For example, the following a.txt file:

Module.exports = "hello world"

See here I believe you already know how the require function loads the module and how to customize the Node.js file loader. So is there a more elegant and simpler solution for Node.js to support loading other types of files such as ts, png, or css? The answer is yes, we can use pirates as a third-party library.

What is pirates?

The library pirates allows us to hijack Node.js 's require function correctly. With this library, we can easily extend the functionality of the Node.js loader.

The usage of pirates

You can use npm to install pirates:

Npm install-save pirates

After successfully installing the pirates library, you can use the module to export the provided addHook function to add hooks:

/ / register.jsconst addHook = require ("pirates"). AddHook;const revert = addHook ((code, filename) = > code.replace ("@ @ foo", "console.log ('foo');"), {exts: [".js"]})

It should be noted that a revert function is returned after calling addHook, which is used to cancel the hijacking of the require function. To verify that the pirates library is working properly, first create a new index.js file and enter the following:

/ / index.jsconsole.log ("@ @ foo")

Then enter the following command on the command line:

$node-r. / register.js index.js

When the above command runs successfully, the console outputs the following:

Console.log ('foo')

Looking at the above results, we can see that the hook we added through the addHook function works. Do you think it's amazing? next, let's analyze how pirates works.

How does pirates work

The bottom layer of pirates is to use the extension mechanism provided by Node.js built-in module module to realize Hook function. As we mentioned earlier, when you use the require function to load a module, Node.js matches the corresponding loader based on the suffix name of the file. In fact, the source code of pirates is not complicated. Let's focus on the core processing logic of addHook function:

/ / src/index.jsexport function addHook (hook, opts = {}) {let reverted = false; const loaders = []; / store the new loader const oldLoaders = []; / / store the old loader let exts; const originalJSLoader = Module._extensions ['.js']; / / the original JSLoader const matcher = opts.matcher | | null; const ignoreNodeModules = opts.ignoreNodeModules! = false; exts = opts.extensions | | opts.exts | | opts.extension | | opts.ext | ['.js'] If (! Array.isArray (exts)) {exts = [exts];} exts.forEach ((ext) {/ /...}}

In order to improve execution efficiency, the addHook function provides matcher and ignoreNodeModules configuration items to implement file filtering operations. Once the list of exts extensions is obtained, the existing loader is replaced with a new loader.

Exts.forEach ((ext) = > {if (typeof ext! = = 'string') {throw new TypeError (`Invalid Extension: ${ext}`);} / / get the registered loader. If it is not found, the default is JSLoader const oldLoader = Module._ extensions [ext] | | originalJSLoader; oldLoaders [ext] = Module._ extensions [ext]; loaders [ext] = Module._ extensions [ext] = function newLoader (mod, filename) {let compile If (! reverted) {if (shouldCompile (filename, exts, matcher, ignoreNodeModules)) {compile = mod._compile; mod._compile = function _ compile (code) {/ / you need to revert to the original _ compile function, otherwise there will be an endless loop mod._compile = compile / / execute user-defined hook function const newCode = hook (code, filename) before compilation; if (typeof newCode! = = 'string') {throw new Error (HOOK_RETURNED_NOTHING_ERROR_MESSAGE);} return mod._compile (newCode, filename);} }} oldLoader (mod, filename);

Looking at the above code, we can see that inside the addHook function, the hook function is implemented by replacing the mod._compile method. That is, before calling the original mod._compile method for compilation, the hook (code, filename) function will be called to execute the user-defined hook function, thus processing the code.

OK, this is the end of the main content of this article, in the actual work, if you want Node.js to directly execute ts files, you can use ts-node or esbuild-register these two libraries. The esbuild-register library uses the Hook mechanism provided by pirates to achieve the corresponding functions.

After reading this, the article "how to add hooks in the require function of Node.js" has been introduced. If you want to master the knowledge of this article, you still need to practice and use it yourself. If you want to know more about related articles, please follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report