During the development process, Node.js is often used, which uses the capabilities provided by V8 to expand the capabilities of JS. In Node.js, we can use the path module that does not exist in JS. In order for us to be more familiar with the application, let us take a look at it~
The Node.js version of this article is 16.14.0, and the source code of this article comes from here Version. I hope that after reading this article, it will be helpful for everyone to read the source code.
Path is used to process the paths of files and directories. This module provides some tool functions that are convenient for developers to develop to assist us in making complex path judgments and improve development efficiency. For example:
Configure aliases in the project. The configuration of the alias makes it easier for us to reference files and avoid searching upwards step by step.
reslove: { alias: { // __dirname directory path 'src' where the current file is located: path.resolve(__dirname, './src'), // process.cwd current working directory '@': path.join(process.cwd(), 'src'), }, }
In webpack, the output path of the file can also be generated to the specified location through our own configuration.
module.exports = { entry: './path/to/my/entry/file.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'my-first-webpack.bundle.js', }, };
Or for folder operations
let fs = require("fs"); let path = require("path"); // Delete the folder let deleDir = (src) => { // Read the folder let children = fs.readdirSync(src); children.forEach(item => { let childpath = path.join(src, item); // Check if the file exists let file = fs.statSync(childpath).isFile(); if (file) { // Delete the file if it exists fs.unlinkSync(childpath) } else { //Continue to detect the folder deleDir(childpath) } }) // Delete the empty folder fs.rmdirSync(src) } deleDir("../floor")
briefly understands the usage scenarios of path. Next, we will study its execution mechanism and how it is implemented based on its usage.
When the path module is introduced and the tool function of path is called, the processing logic of the native module will be entered.
Use the _load
function to use the module name you introduced as the ID to determine that the module to be loaded is a native JS module. After that, the loadNativeModule
function will be used to use the id to find the corresponding ASCII code from _source
(the source code string that saves the native JS module). The data is loaded into the native JS module.
Execute the lib/path.js file and use process to determine the operating system. Depending on the operating system, there may be differential processing of operating characters in file processing, but the method is roughly the same. After processing, it is returned to the caller.
resolve returns the absolute path of the current path
resolve splices multiple parameters in sequence to generate a new absolute path.
resolve(...args) { let resolvedDevice = ''; let resolvedTail = ''; let resolvedAbsolute = false; // Detect parameters from right to left for (let i = args.length - 1; i >= -1; i--) { ... } //Normalized path resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\', isPathSeparator); return resolvedAbsolute? `${resolvedDevice}\${resolvedTail}` : `${resolvedDevice}${resolvedTail}` || '.'; }
Get the path according to the parameters, traverse the received parameters, start splicing when the length of the parameters is greater than or equal to 0, perform non-string verification on the spliced path, if there are any parameters that do not match, throw new ERR_INVALID_ARG_TYPE(name, 'string', value)
, if the requirements are met, the length of the path will be judged. If there is a value, += path will be used for the next step.
let path; if (i >= 0) { path = args[i]; // internal/validators validateString(path, 'path'); // If the length of path is 0, it will jump directly out of the for loop of the above code block if (path.length === 0) { continue; } } else if (resolvedDevice.length === 0) { //The length of resolvedDevice is 0, assign the value to path as the current working directory path = process.cwd(); } else { // Assign the value to the environment object or the current working directory path = process.env[`=${resolvedDevice}`] || process.cwd(); if (path === undefined || (StringPrototypeToLowerCase(StringPrototypeSlice(path, 0, 2)) !== StringPrototypeToLowerCase(resolvedDevice) && StringPrototypeCharCodeAt(path, 2) === CHAR_BACKWARD_SLASH)) { //Judge the path to non-empty and absolute paths to get the path path = `${resolvedDevice}\`; } }
Try to match the root path, determine whether there is only one path separator ('') or the path is an absolute path, then mark the absolute path and set rootEnd
interception flag to 1 (subscript). If the second item is still a path separator (''), define the interception value as 2 (subscript), and use last
to save the interception value for subsequent judgment.
Continue to determine whether the third item is a path separator (''). If so, it is an absolute path, and the rootEnd
interception identifier is 1 (subscript), but it may also be a UNC path (servernamesharename, servername server name). sharename shared resource name). If there are other values, the intercepted value will continue to increment and read the following values, and use firstPart
to save the value of the third bit so that the value can be obtained when splicing the directory, and keep the last and intercepted values consistent to end the judgment.
const len = path.length; let rootEnd = 0; // Path interception end subscript let device = ''; // Disk root D:, C: let isAbsolute = false; // Whether it is the disk root path const code = StringPrototypeCharCodeAt(path, 0); // path length is 1 if (len === 1) { // There is only one path separator for absolute path if (isPathSeparator(code)) { rootEnd = 1; isAbsolute = true; } } else if (isPathSeparator(code)) { // Might be a UNC root, starting with a delimiter , at least one of which is some kind of absolute path (UNC or other) isAbsolute = true; // Start matching the double path separator if (isPathSeparator(StringPrototypeCharCodeAt(path, 1))) { let j = 2; let last = j; // Match one or more non-path delimiters while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j < len && j !== last) { const firstPart = StringPrototypeSlice(path, last, j); last = j; // Match one or more path separators while (j < len && isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j < len && j !== last) { last = j; while (j < len && !isPathSeparator(StringPrototypeCharCodeAt(path, j))) { j++; } if (j === len || j !== last) { device= `\\${firstPart}\${StringPrototypeSlice(path, last, j)}`; rootEnd = j; } } } } else { rootEnd = 1; } // Detect disk root directory matching example: D:, C: } else if (isWindowsDeviceRoot(code) && StringPrototypeCharCodeAt(path, 1) === CHAR_COLON) { device = StringPrototypeSlice(path, 0, 2); rootEnd = 2; if (len > 2 && isPathSeparator(StringPrototypeCharCodeAt(path, 2))) { isAbsolute = true; rootEnd = 3; } }
Detect the path and generate it, check whether the disk root directory exists or resolve whether resolvedAbsolute
is an absolute path.
//Detect the disk root directory if (device.length > 0) { // resolvedDevice has value if (resolvedDevice.length > 0) { if (StringPrototypeToLowerCase(device) !== StringPrototypeToLowerCase(resolvedDevice)) continue; } else { // resolvedDevice has no value and is assigned the value of the disk root directory resolvedDevice = device; } } // Absolute path if (resolvedAbsolute) { // There is an end loop if the disk root directory exists (resolvedDevice.length > 0) break; } else { // Get the path prefix for splicing resolvedTail = `${StringPrototypeSlice(path, rootEnd)}\${resolvedTail}`; resolvedAbsolute = isAbsolute; if (isAbsolute && resolvedDevice.length > 0) { // The loop ends when the disk root exists break; } }
join performs path splicing based on the incoming path fragments
Receive multiple parameters, use specific separators as delimiters to connect all path parameters together, and generate a new normalized path.
After receiving the parameters, verify them. If there are no parameters, it will return '.' directly. Otherwise, it will traverse and verify each parameter through the built-in validateString
method. If there is any violation, directly throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
join
The function of the escape character is that when used alone, it is considered to escape the string after the slash, so double backslashes are used to escape the backslash ('').
Finally, the concatenated string is verified and returned in format.
if (args. length === 0) return '.'; let joined; let firstPart; // Detect parameters from left to right for (let i = 0; i < args.length; ++i) { const arg = args[i]; // internal/validators validateString(arg, 'path'); if (arg. length > 0) { if (joined === undefined) // Assign the first string to joined, and use the firstPart variable to save the first string for later use joined = firstPart = arg; else // joined has a value, perform += splicing operation joined += `\${arg}`; } } if (joined === undefined) return '.';
Under the window system, network path processing is required due to the use of backslash ('') and UNC (mainly referring to the complete Windows 2000 name of resources on the LAN) path, ('') represents is a network path format, so the join
method mounted under win32 will intercept by default.
If a backslash ('') is matched, slashCount
will be incremented. As long as there are more than two backslashes ('') matched, the spliced path will be intercepted and manually spliced and escaped. backslash ('').
let needsReplace = true; let slashCount = 0; // Extract the code code of the first string in sequence according to StringPrototypeCharCodeAt, and match it with the defined code code through the isPathSeparator method if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 0))) { ++slashCount; const firstLen = firstPart.length; if (firstLen > 1 && isPathSeparator(StringPrototypeCharCodeAt(firstPart, 1))) { ++slashCount; if (firstLen > 2) { if (isPathSeparator(StringPrototypeCharCodeAt(firstPart, 2))) ++slashCount; else { needsReplace = false; } } } } if (needsReplace) { while (slashCount < joined.length && isPathSeparator(StringPrototypeCharCodeAt(joined, slashCount))) { slashCount++; } if (slashCount >= 2) joined = `\${StringPrototypeSlice(joined, slashCount)}`; }
Execution results sorting
resolve | join | ||
---|---|---|---|
has no parameters | . | The absolute path of the current file | .|
The parameters have no absolute | path. The absolutepath of the current file is spliced | in order. | |
The first parameter is the absolute path. | The parameter path overwrites the absolute path of the current file and isspliced into the absolute path of | subsequent non-absolute paths. | The path |
post-parameter is an absolute path | parameter. The path overwrites the absolute path of the current file and overwrites | the path spliced by the | pre-parameters.|
The first parameter is (./) | and has subsequent parameters. The absolute path splicing parameter of the current file has no subsequent parameters. The absolute path of the current file is The path | has subsequent parameters, and the path spliced by the subsequent parameters has no subsequent parameters. (./) | |
The post-parameter has (./) | The parsed absolute path splicing parameter | has subsequent parameters. The spliced path has no subsequent parameters, and the splicing (/) | |
The first parameter is (../) | and there are subsequent parameters. The splicing parameters after the last level directory covering the absolute path of the current file have no subsequent parameters. The last level directory covering the absolute path of the current file | has subsequent parameters. Splicing There are no subsequent parameters for subsequent parameters. (../) | |
The post-parameter has (../ | ). The upper-level directory where (../) appears will be overwritten. The number of subsequent parameters will be overwritten. The upper-level directory will be overwritten. After, return (/), subsequent parameters will be spliced and | the upper directory that appears (../) will be overwritten. How many layers appear in the suffix will be covered. After the upper directory is overwritten, the parameter splicing |
read. After source code, the resolve
method will process the parameters, consider the form of the path, and throw out the absolute path at the end. When using it, if you are performing operations such as files, it is recommended to use the resolve
method. In comparison, the resolve
method will return a path even if there are no parameters for the user to operate, and the path will be processed during the execution process. The join
method only performs standardized splicing of the incoming parameters, which is more practical for generating a new path and can be created according to the user's wishes. However, each method has its advantages. You should choose the appropriate method according to your own usage scenarios and project needs.