How to get current filename without path and extension with webpack? - javascript

I can get the relative file path with __filename, and sure I could hack it apart to get just the basename with some JS-fu, but I want to do this at compile-time.
DefinePlugin will let me inject some globals like I want, but AFAICT I can't have a "dynamic" global based on the current file.
So how can I do this?
e.g. given I am in the file assets/scripts/lib/components/bpm/RecordAttendancePopup.jsx, how can I get webpack to inject a constant like __basename that evaluates to "RecordAttendancePopup"?

Generic version
If you're using modules or any supported bundler, you can access the pre-existing import.meta.url and manually extract the filename:
const filename = import.meta.url // Get current path
.split(/[\\/]/).pop() // Get current filename
.replace(/\.[^.]+$/, ''); // Drop extension
or you can use a better parser (which may or may not work depending on your config)
const path = require('path');
const filename = path.parse(import.meta.url).name;
Webpack-specific version
This is what I use in my webpack.config.js:
const path = require('path');
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
__filebasename: webpack.DefinePlugin.runtimeValue(
info => JSON.stringify(path.parse(info.module.resource).name)
)
})
]
};
and then I'll have the __filebasename variable available everywhere in my source. Works in Webpack 4 and 5.

I guess there is no other way than creating your own "DefinePlugin" based on https://github.com/webpack/webpack/blob/master/lib/DefinePlugin.js to get what you want.

Related

How do I access a JSON file relative to my calling module in Node?

I'm defining a package, PackageA, that has a function (parseJson) that takes in a file path to a json file to parse. In another package, PackageB, I want to be able to call PackageA using a file I specify with a local path from PackageB. For example, if file.json is in the same directory as packageB, I'd like to be able to call PackageA.parseJson('./file.json'), without any extra code in PackageB. How would I do this? It seems that require requires a path from PackageA to the file, which is not what I want.
Edit: Currently, parseJson looks something like this:
public parseJson(filepath) {
let j = require(filepath);
console.log(j);
}
and PackageB is calling it like this:
let a = new PackageA();
a.parseJson("./file.json");
file.json is in the same directory as PackageB.
CommonJS modules have __dirname variable in their scope, containing a path to directory they reside in.
To get absolute path to RELATIVE_PATH use join(__dirname, RELATIVE_PATH) (join from path module).
example:
// PackageB .js file
const Path = require('path')
const PackageA = require(/* PackageA name or path */)
const PackageB_jsonPathRelative = /* relative path to json file */
// __dirname is directory that contains PackageB .js file
const PackageB_jsonPathAbsolute = Path.join(__dirname, PackageB_jsonPathRelative)
PackageA.parseJson(PackageB_jsonPathAbsolute)
UPDATED
If you can't change PackageB, but you know exactly how PackageA.parseJson is called by PackageB (e.g. directly, or through wrappers, but with known depth), then you can get path to PackageB from stack-trace.
example:
// PackageA .js file
// `npm install stack-trace#0.0.10` if you have `ERR_REQUIRE_ESM` error
const StackTrace = require('stack-trace')
const Path = require('path')
const callerFilename = (skip=0) => StackTrace.get(callerFilename)[skip + 1].getFileName()
module.exports.parseJson = (caller_jsonPathRelative) => {
// we want direct caller of `parseJson` so `skip=0`
// adjust `skip` parameter if caller chain changes
const callerDir = Path.dirname(callerFilename())
// absolute path to json file, from relative to caller file
const jsonPath = Path.join(callerDir, caller_jsonPathRelative)
console.log(jsonPath)
console.log(JSON.parse(require('fs').readFileSync(jsonPath)))
}

node.js can't find module from relative path

I have two simple files in node.js and want to export two classes from one file and import them in the other. I'm using:
module.exports = {TrigInter, Hilbert};
Now, if I call require, it only works with the absolute file path:
const lib = require("/Users/username/documents/atom/project_folder/lib.js");
and not with the relative file path:
const lib = require("./lib.js");
eventhough the two files are both located in the "project_folder". I'm pretty sure, I tried the exact same thing before and it worked with the relative path. I don't see what I'm doing wrong. What am I missing?
Absolute path is not best practice to use, instead you can use path join method like
const path = require('path');
let your_file_path = path.resolve(__dirname, '/lib.js');
https://www.digitalocean.com/community/tutorials/nodejs-how-to-use__dirname

Update (write to) an object in a separate JS file using Node

I'm fairly new to Node, and am wracking my brains on how to achieve the following:
I have a config file that looks something like this:
// various es imports
export default {
input: {
index: 'src/index.ts',
Button: 'src/Button/index.ts',
Spinner: 'src/Spinner/index.ts',
'icons/Notification': 'src/_shared/components/icons/Notification.tsx',
'icons/Heart': 'src/_shared/components/icons/Heart.tsx',
},
//.. other properties
}
From my node script, i need to somehow read this file and do the following:
Delete any entries in the input object that have a key starting
with icons/
Append new entries to the input object.
Write these changes back to the original config file.
Is there a recommended way to do this in Node, i've been looking at a couple of libs, like replace-in-file but none seem to be suited to this particular case.
Just faced the same concern, here is how I solved it :
1. Gets your file content
If it is not a .js file, then use fs.readFileSync (or fs.readFile) like so :
const fs = require('fs');
const path = require('path');
const myObjectAsString = fs.readFileSync(
path.join( process.cwd(), 'my-file.txt' ), // use path.join for cross-platform
'utf-8' // Otherwise you'll get buffer instead of a plain string
);
// process myObjectAsString to get it as something you can manipulate
Here I am using process.cwd(), in case of a CLI app, it will gives you the current working directory path.
If it is a .js file (eg. a JavaScript config file like webpack.config.js for instance), then simply use require native function, as you would do with regular internal file or NPM module, and you will get all module.export content :
const path = require('path');
const myObject = require( path.join( process.cwd(), 'myFile.js') );
2. Modify your object as you want
// ...
myObject.options.foo = 'An awesome option value';
3. Then rewrite it back to the file
You can simply use fs.writeFileSync to achieve that :
// ...
fs.writeFileSync( path.join(process.cwd(), 'my-file.txt', myObject );
If you want to write a plain JavaScript Object, then you can use util.inspect() native method and you may also use fs.appendFileSync :
// ...
// If we wants to adds the 'module.exports = ' part before
fs.writeFileSync( process.cwd() + '/file.js', 'module.exports = ');
// Writes the plain object to the file
fs.appendFileSync( process.cwd() + '/file.js', util.inspect(options));

How to dynamically resolve nodejs required module's path based on caller script's path?

I am kind of new to Javascript programming. Currently I am trying to write a test for Javascript files in existing codebase that contains other programming languages. The structure is like below.
src/js/path1/path2/path3/path4/path5/
Rectangle.jsx
Circle.jsx
test/js/path1/path2/path3/path4/path5/
RectangleTest.jsx
CircleTest.jsx
The content of RectangleTest.jsx is below
import Rectangle from './../../../../../../../src/js/path1/path2/path3/path4/path5/Rectangle';
describe('<Rectangle>', () => {
it('Should show content', () => {
assert.ok(true);
});
});
As you can see, I need to set the path as a very long relative path ./../../../../../../../src/js/path1/path2/path3/path4/path5. It will be very exhaustive for I prefer something like below.
import Rectangle from './Rectangle';
Since the path of the test file and the tested file is pretty similar, it should be possible to calculate the path to be resolved by the import.
Is there a way to do that?
I am using mocha for the testing framework. I uploaded the sample code to Github (link), so you can see it.
You can use the __dirname global node variable which contains the absolute path to the current file. However you have to use require() instead of import ... because import does not support dynamic paths.
if your absolute path only contains one test name you can get away with:
const path = require('path');
const retanglePath = path.join(__dirname.replace('/test/', '/src/'), 'Rectangle'));
const Rectangle = require(rectanglePath).default;
Note: the .default is for ES6 exports that are converted with babel.
Hope this helps.
Edit: Here is a solution that also works with other test folder names in the absolute path (replace the path relative for your needs):
const path = require('path');
const basePath = path.join(__dirname, '../../../../');
const srcPath = __dirname.replace(basePath + 'test', basePath + 'src');
const Rectangle = require(path.join(srcPath, 'Rectangle')).default;
After researching about how nodejs resolve module, it turns out that it's possible to override the require function behavior by overriding module module.
So I write a file called bootstrap.js that contains code below
let path = require('path');
const BASE_DIR = __dirname;
const SRC_DIR = path.resolve(BASE_DIR + '/../../src/js');
var Module = require('module');
Module.prototype.require = new Proxy(Module.prototype.require, {
apply(target, thisArg, argumentsList){
let name = argumentsList[0];
let isLocal = thisArg.filename.startsWith(BASE_DIR) &&
name.startsWith('./');
if (isLocal) {
let testFileDir = path.dirname(thisArg.filename)
let testPath = testFileDir.replace(BASE_DIR, '');
let srcPath = SRC_DIR + testPath;
let relativePath = path.relative(testFileDir, srcPath);
argumentsList[0] = relativePath + '/' + name;
}
return Reflect.apply(target, thisArg, argumentsList)
}
});
The structure now is like this
src/js/path1/path2/path3/path4/path5/
Rectangle.jsx
Circle.jsx
test/js/
bootstrap.js
path1/path2/path3/path4/path5/
RectangleTest.jsx
CircleTest.jsx
To execute the test, I use the statement below
nyc mocha --recursive \
--require test/js/bootstrap.js \
--compilers js:babel-core/register \
test/js/**/*.{js,jsx}
Using the code above, I can check the caller script and called module and see if the caller script resides in the test directory. If it does then, it will see if the module called by using relative path.
The advantage is that, we still can use import statement.
import Rectangle from './Rectangle';
describe('<Rectangle>', () => {
it('Should show content', () => {
assert.ok(true);
});
});
The downside is for now the test file cannot call relative path in the test directory. Relative path will now only resolve in source path. But it's good for now. I am wondering if we can check if a module is resolvable or not.
The updated source code can be read here.

How to get original file path in the script with webpack?

An example code:
//in the file app.module.js
module.exports = framework.module("app", [
require('./api/api.module').name
])
//in the file app/api/api.module.js
module.exports = framework.module("app.api", [
])
Here are two dependent modules named 'app' and 'api'.
Module name is always same as file path to the module file (except module.js part, e.g. for file at app/api/api.module.js module name is 'app.api').
Is it possible to make webpack provide a filename of the included file during compilation, so following can be done?
//in the file app.module.js
module.exports = framework.module(__filename, [
require('./api/api.module').name
])
//in the file app/api/api.module.js
module.exports = framework.module(__filename, [
])
Where __filename is an actual path to the file folder.
It does not really matter what's format of name of the module, but it should be unique (for framework reasons) and lead to the module location (for debug reasons).
Update:
I've solved it for myself - this can be done by custom webpack loader which substitutes a certain placeholder with file path string. But anyway question is still open.
I know you said you resolved this yourself, yet, here's my take on it.
Your solution includes using a custom loader, however, maybe you could have solved it in a different way.
First step, in your webpack.config add these in the config object:
context: __dirname, //set the context of your app to be the project directory
node: {
__dirname: true //Allow use of __dirname in modules, based on context
},
Then, add this in your list of plugins:
new webpack.DefinePlugin({
SEPARATOR: JSON.stringify(path.sep)
})
This will replace all SEPARATOR instances in your modules with whatever the correct path separator is, for the system you are working on (you will have to require('path') in your webpack.config for this to work)
And finally in whatever module you want you can now get its name by doing
var moduleName = __dirname.replace(new RegExp(SEPARATOR, 'g'), '.');
So, for example
//in the file app.module.js
var moduleName = __dirname.replace(new RegExp(SEPARATOR, 'g'), '.');
module.exports = framework.module(moduleName, [
require('./api/api.module').name
])
//in the file app/api/api.module.js
var moduleName = __dirname.replace(new RegExp(SEPARATOR, 'g'), '.');
module.exports = framework.module(moduleName, [
])

Categories