How does Nodejs handle relative paths? - javascript

I have a question regarding Nodejs's path-handling.
I know that they have a page about that in the docs but it didnt contain what I needed.
So basically, I have a file that includes a relative path referencing a file (png in this case).
Now, based on where I call the file from, the picture is either found or not found (as the point in the fileSystem from where its called changes).
I am using the 'sharp' framework, 'sharp('./picture.png')' is similar to require.
Example:
File 'render.js' :
const pic = sharp('./picture.png')
Calling:
cmd\examplePath> node render.js //picture is found
cmd> node ./examplePath/render.js //picture is not found
The location of the picture relative to the file stays the same at all times!
My question now is if what I have described is to be expected from Nodejs or if there is something wrong. What would I need to do to be able to call the file from anywhere and have it still work?
Any tips are appreciated.
Thanks :)

Normally file handling in nodejs such a fs.open() just resolves a relative path versus the current working directory in nodejs. The current working directory will be whatever the OS working directory was when you started your nodejs app.
The current working directory is not necessarily the same as the directory where your script is located because the current working directory might be different than where your script is located when your nodejs program was started.
So, in your two command line examples, each is starting with a different current working directory, thus one works and one doesn't.
In general in nodejs, it is not advisable to rely on what the current working directory is because this lessens the ability to reuse your code in other projects where it might be loaded from a different directory. So, if the file you are trying to reference is in a known location relative to your script's file system location, then you would typically build a full path, using either __dirname (in CommonJS modules) or import.meta.url or import.meta.resolve() (in ESM modules).
For example, if the image is in the same directory as your script, then you could do this in a CommonJS module:
const path = require('path');
const fullPath = path.join(__dirname, "picture.png");
const pic = sharp(fullPath);
In an ESM module, you could do:
import path from 'path';
const __dirname = new URL('.', import.meta.url).pathname;
const fullPath = path.join(__dirname, "picture.png");
const pic = sharp(fullPath);
My question now is if what I have described is to be expected from Nodejs or if there is something wrong.
Yes, that is to be expected. Your are starting your program with different current working directories and have code that depends upon the current working directory being a certain value (because of your usage of a relative path).
What would I need to do to be able to call the file from anywhere and have it still work?
Build an absolute path using your script's directory as the anchor location as shown above.
Note that require() or import use completely different file searching logic and have some built-in behavior that is relative to the module's location that is running them. But, they are the exception, not the rule. Operations in the fs module (or other modules that use the fs module) use the current working directory as the base path if you supply a relative path.

Related

Problem writing to file fs module, apparently the path is positioned at the root level

I'm practicing with typescript and I want to write a file using fs module but I don't know if is this a noob question or I'm doing something wrong but my project look like this:
root
-> dir (here are the js result from tsc)
-> src
--> data
---> data.json
--> service
---> service.ts
--> index.ts
-> package.json
-> tsconfig.json
And at service.ts apparently the path is:
let filePath = path.join('./','src','data','data.json') // this works
fs.writeFile(filePath, JSON.stringify(data,null,2), 'utf8', (err)=>{ if(err){ return console.log(err);}})
So I don't know why the path is positioned at the root level
If I try "../data/data.json" I get ENOENT ERROR no such file or directory
Is it ok?
Filesystem operations with relative paths always use the Current Working Directory - a concept explained here: https://en.m.wikipedia.org/wiki/Working_directory
When you run Node.js, you do so while being in a particular directory. For example, scripts such as npm start are usually executed in the root-level directory of a repository/project - this causes all relative paths to resolve starting from there. Note, however, that this may be different in production - it is possible for Docker, PM2, systemd, or any other tool to run your script while being in a different working directory (this can often be configured).
To inspect your current working directory in Node.js, use https://nodejs.org/api/process.html#processcwd
It is also possible to build paths relative to the directory of the JS file. This tutorial shows various examples on how to do that: https://www.digitalocean.com/community/tutorials/nodejs-how-to-use__dirname
It is important to remember that require() uses paths relative to __dirname, but fs resolves relative to CWD.

Why do do relative paths in Node.js for FS module and require point at different locations?

When we run the server on Node JS we should use paths relative to which directory we launched node in. To solve this issue we ought to use the path module like this:
fs.createReadStream(path.join(__dirname, '..', '..', 'data', 'someData.csv'))
But when we use require we can just place a relative path to the file in which we're requiring, wthout having to consider where node is launched from:
const {data} = require('../../models/data.model');
Can you please explain: why does it work that way?
The fs module handles relative paths relative to the process's working directory, e.g. where it's launched. You can use process.cwd() to figure out where that is.
When it comes to require, that's a bit of a special case. When your script/module gets initialized and called, NodeJS will under the hood create a brand new require function. You can read more about that here.
Basically, your module has its own require which knows of __dirname and makes all relative paths passed to it be relative to that path.

File Pathing in Node.js

I have a folder that looks like
Main > models > file1.ejs
|
|> routes > file2.ejs
Two require file1 from file2 the code is const variable = require("../models/file1.ejs)
Let's say I do not want to require the model relatively, why does
const variable = require("/models/file1.ejs") not work.
Isnt "/" meaning to start at the root directory?
If we have an extensive nest of folders within another, how can we avoid doing "../../../../" in our file pathing?
Additionally, why does a code in JS that looks like Comment = require("./models/comment")
work but does not work when you do Comment = require("models/comment")
Or something in ejs that looks like <%- include("partials/header") %>
<%- include("/partials/header") %> does not work.
Is there anyway to always start from the root directory during pathing? Starting with "/" seems to end up causing an error when its suppose to make my pathing start from the root directory.
For example consider the below code,
var filename = "file1.ejs";
var fullpath = __dirname + "/models/" + filename;
read the docs about __dirname here
This is how you can provide the absolute path, where the __dirname will provide you with the path until the current working directory.
Where as relative paths work as below,
For example, if you trying to access file file1.ejs which is inside models directory from other file file2.ejs which is inside directory models2 where the models2 directory is also inside models then you may provide the relative path,
ie, the folder structure is as follows,
basePath/models/file1.ejs
basePath/models/models2/file2.ejs
and access file1.ejs from file2.ejs as below,
var path = '../file1.ejs' // going one folder back and getting inside parent models directory where the file1.ejs resides
Isnt "/" meaning to start at the root directory?
No. "/" refers to your existing file directory.
Additionally, why does a code in JS that looks like Comment = require("./models/comment") work but does not work when you do Comment = require("models/comment")
"/" is how node differentiates if they should pick up locally or somewhere in your installed modules. Imagine you have Express installed in your project and you have a file express.js. Then require('./express') will import your local file but require('express') will import the installed library instead.
If we have an extensive nest of folders within another, how can we avoid doing "../../../../" in our file pathing?
I believe you will need Babel. I haven't found any good sample on the web, but here are some useful materials.
Side note, I have worked on node project with related setup, but the development experience was awful as my editor couldn't pick up the resolved path and was struggling with auto-complete and intellisense. (but I personally believe the setup way was wrong or depreciated).
Resources:
Import module from root path in TypeScript
https://www.npmjs.com/package/babel-plugin-module-resolver
https://webpack.js.org/configuration/resolve/

Node.js is unable to find a file in another folder

I have a Discord bot I'm maintaining since a year, and a couple of months ago I changed a bit the file structure to clean it up and make it easier for me to know what's going on.
The thing is, whenever I try to request a file (with require) that is in a folder located in the bot's root directory, sometimes it works with "./" and other times it works with "../"
The current file structure is:
----commands
-------commands.js(multiple files)
----images
-------halloween
----------images.png/jpg(multiple images)
----logs
-------bot.log
----modules
------logger.js
----settings
-------config.json
-emojis.json
-gifs.json
-index.js
Following the structure above, when for example I try to request one of the halloween images in a command, the logical thing to me would be to use "../images/halloween/image.png", but instead I have to use "./images/halloween/image.png" as if the "images" folder is within the "commands" folder
In one of the commands I have to use:
const logs = require("../modules/logger");
const background = await Canvas.loadImage("./images/halloween/background.jpg");
I would like to know why this happens. It really messes with my brain seeing an error saying that a file was not found only because node.js decided that this time the parent directory is "./" instead of "../"
Assuming your commands file is making file system calls (because you're accessing an image from it), the directory you invoke your script from can matter. Make sure you're using the path utility to resolve your file locations. See NodeJS accessing file with relative path for more details.

How to Handle Assets in NodeJS Module

I have a NodeJS module that is supposed to translate an XML to HTML via an XSL file. The problem is I need to bundle the XSL with the rest of the module.
Right now it's inside the source folder and I use it via:
function createHtml(xml) {
const fs = require('fs');
const xsltProcessor = require('xslt-processor');
var xsl = fs.readFileSync('./src/xsl/html-export.xsl', 'utf-8');
return xsltProcessor.xsltProcess(xsltProcessor.xmlParse(xml), xsltProcessor.xmlParse(xsl));
}
Of course I made sure to have this folder in the files property of the package.json:
"files": [
"src"
],
This setup works inside the module, but not when I publish the module and require it from a second module. Then I need to copy the XSL file into the second module's path as well.
That's not really working for me. I want to access the file relative to the JavaScript source file, not the current NodeJS application.
I tried using a different kind of path (html-export.xsl, ./html-export.xsl, ./xsl/html-export.xsl) but nothing else works even inside the module. So I can't publish it to try it out elsewhere.
The good news is: the XSL file is actually included inside the module during the publishing; when I install it the module in the node&lowbar;modules/ folder contains the XSL. So it's "just" the above code that's not working, because the path is resolved relative to the second module (i.e. the one with the require), not relative to the original one (i.e. the one inside the node&lowbar;modules/).
How do I handle assets in NodeJS so they are bundled with the module they belong to?
This question seems related, but the answer is exactly what I did, and it doesn't work for me for some reason.
It appears you only want to use the .xsl file internally in your package, which is something that I originally misunderstood..
In order to accomplish this, you can use the path package to resolve the path of your .xsl file.
Like this:
function createHtml(xml) {
const fs = require('fs');
const path = require('path');
const xsltProcessor = require('xslt-processor');
var xsl = fs.readFileSync(path.resolve(__dirname, './src/xsl/html-export.xsl'), 'utf-8');
return xsltProcessor.xsltProcess(xsltProcessor.xmlParse(xml), xsltProcessor.xmlParse(xsl));
}
In order to demonstrate how to do this, I have built an example NPM package for you to review. While this package doesn't use an .xsl file, it uses a .txt file - the logic remains the same.
The readme file within the package contains instructions on how to use it..
You can find the package on NPM here
You can find the source code to the package here

Categories