How to Handle Assets in NodeJS Module - javascript

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_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_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

Related

How does Nodejs handle relative paths?

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.

Can I specify a config file that an executable compiled with electron-builder can access after packaging?

I'm building an Electron app where a client asks a server for information stored in a JSON file on the server. How can I compile the server app (using electron-builder or other) and then include a JSON file that the compiled executable has access to?
I've look through the Electron and electron-builder docs but I was unable to find any relevant information.
In the end, I'd need the JSON file to be located outside of the packaged server app so that it can be freely modified by the person using it.
I appreciate any and all help!
EDIT: I have since solved my issue. Please refer to the post below explaining my solution!
After asking on the Electron Slack chatroom, I was informed that I can use the fs module from Node to reference the file's location and use electron-builder's extraResources option to have that file be moved outside the EXE after compilation.
For example, if you wanted to reference config.json, you would reference it like so in your main.js file:
const { readFileSync } = require('fs');
var configFile = JSON.parse(readFileSync('./config.json'));
Then, in your package.json file, you would use extraResources to tell electron-builder what file to pull from where:
"build": {
"extraResources": [
{
"filter": ["./config.json"]
}
]
}
And of course, with filter being an array, you can continue to specify files that you'd like to remain external just by deliminating them with a comma!
I hope this helps whoever else may have been having issues with it!

Wepback require from variable

I am making console utility which accepts a path to configuration file as a console argument.
F.e: utility -f /path/to/file.js
I need to require this file to read configuration. Is it possible to handle this with webpack? As I understand context can not help me in this situation.
Thanks.
P.S. I'm already using webpack.
P.S Solution is to use something like: eval('require')(dynamicPath)
Webpack can only do a dynamic require like this if the file to be required is available at compile time. For example, if you require a "dynamic" file path, like
require('./assets/images/' + someVariable + '.png')
Then under the hood, Webpack will include all images matching that pattern in the bundled require code. It basically will include all files matching the regex:
/.\/assets\/images\/*.png/
I would probably try putting the config file in a specific folder, and using require on that folder:
require('./configs/' + process.env.CONFIG_NAME);
This way Webpack will only include all files in the configs folder.
The Webpack documentation is horrible but there is more information on the dynamic requires page.
If you are passing in a config file as an argument to a node process, it will be accessible in the process.argv array of command line arguments. I don't know if you are using some other framework (like the excellent commander) to help with making command line programs, but we can just slice the array to find what we need.
To resolve a path from the directory the script is launched in, you can use process.cwd() - this returns an absolute path to the working directory of the node process.
Finally you can use path.resolve(processPath, configPath) (docs) to generate a path that is always guaranteed to resolve to the config. You can then require this path.
You probably need to do this first. The top of your file could look something like this:
/* relevant require() calls for necessary modules */
var path = require('path');
// first two arguments are node process and executed file
var args = process.argv.slice(2);
var configIndex = args.findIndex('-f') + 1;
var configPath = path.resolve(process.cwd(), args[configIndex]);
var config = require(configPath);
/* the rest of your code */

Loading Node.js Module using Browserify

I am using Browserify (http://browserify.org/) to load a module in JavaScript. I keep getting the following error:
I have no idea why this is happening. I have a "package.json" file in a directory called "wordnet-develop", which is located in the same location as the JavaScript file.
Originally I thought that there might be a path problem. However, I did the same exact thing but with a test.js file, and it worked. So, I think that there may be something wrong with using package.json.
The beginning of the package.json file:
The beginning of my JavaScript file:
The directory containing the javascript file:
The directory (seen above as "wordnet-develop")containing the package.json file:
UPDATE
I replaced var WordNet = require('./wordnet-develop/node-wordnet'); with var WordNet = require('./wordnet-develop/lib/wordnet'); as suggested by klugjo.
It may have worked, but now I am getting a new error message:
This happened again but with 'async' module missing. I checked lib/wordnet, and it included requirements for bluebird and async, so that's probably the error source.
However, I now have no idea what to do. I'm new to node.js and modules, so I'm unfamiliar with solutions. Am I supposed to parse all of the code and find all the required modules online? Shouldn't they have been included in the module? Is the problem that I'm trying to use a node.js module in vanilla JavaScript?
I don't think what you are trying to do is supported: you have to link directly to the entry javascript file of the node-wordnet library.
Replace
var WordNet = require('./wordnet-develop/node-wordnet');
With
var WordNet = require('./wordnet-develop/lib/wordnet');

locating path of a file in meteorjs

I have a project in meteorjs that is using the nodes filesystem to read file, but I am not able to locate the file to be read.
My file Location
Server
- startup
- app.load.coffee
- myfileToBeRead.txt
My try in app.load.coffee
fs = Npm.require('fs')
console.log fs.readFileSync 'server/startup/myfileToBeRead.txt'
I am not able to read the file as it says
Error: ENOENT, no such file or directory 'server/startup/myfileToBeRead.txt'
I think since meteor merges everything in a js file, I have to add full path to the file.
I have tried other paths aswell (with the full path, without the full path). Can you point me out to the correct direction here?
Thank you
Well with the answer from David, I also found that I could do this with the assets/app directory of the project. All I had to do was add the file to a directory named private. This would also help me write to a file inside the directory aswell.
fs = Npm.require('fs')
console.log fs.readFileSync "assets/app/myfileToBeREad", 'utf8'
if the file should be checked in
This is the easy case - just place the file in your private directory and access it with the assets api. For more examples, see my blog post on exactly this subject.
if the file should exist somewhere else on the server
Use an absolute path to a directory not associated with your project, e.g. /tmp or /home/foo/bar. Directories inside of a meteor project get jumbled up after you bundle and deploy your app, so their existence can't be counted on. Using your example above it should work if you do something like:
var fs = Npm.require('fs');
fs.readFileSync('/tmp/myfileToBeRead.txt');

Categories