resolving a path in node when using .mjs files - javascript

I'm writing a quick/simple deploy script in node using the module format mjs so that I can use the module syntax.
One of the things I'm doing in the file is resolving an absolute path from a relative one based on the current modules' directory.
In the past I would have done something like:
const path = require("path")
const distPath = path.resolve(__dirname, "..", "dist")
But in module js I lose the __dirname symbol and get the error:
ReferenceError: __dirname is not defined
So I hop online to see what the mjs version of this is (woof, searching for "resolve path in mjs" or any permutation on that is rough b/c everything points to the original way of doing it :P), and from what I learned I can do this:
import { dirname, resolve } from "path"
import { fileURLToPath } from "url
const distPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "dist")
Which works, but is a handful to type.
If this is the correct way of doing it then I'll just memorize the new approach, but this feels like such a common task that there must be a better way of doing it in module js and I just haven't found it.
Any alternatives?

Related

Is it possible to resolve imported module path at compile time in Webpack?

I want to create build that will, based on env variable, include just specific components in the bundle.
For example when I import component like this:
import Module1 from '#/components/Module1'
I want the path to actually be converted so that imports looks like this:
import Module1 from '#/components/brands/' + process.env.APP_BRAND + 'Module1'
Now I don't want to do this for all imports, just for certain ones (eg. modules that have branded version), so maybe I'd like to prefix import path with something like !brand! which will be indicator to apply path transform.
So above logic should executed only for import paths such as:
import Module1 from '!brand!#/components/Module1'
And if process.env.APP_BRAND is falsy then I just want to stick to the original import (eg. just remove !brand! prefix from the path).
So the important aspect is that I want this to be done at build time so that Webpack can statically analyze modules and create optimized build. Is this possible to do? I suppose I need to somehow add this path transformation logic but how should I do that? Should I create a loader, plugin or is there an existing solution?
I am using Webpack 5.
Webpack provides a module resolve functionality that you can use here. Read the docs here. As an example, in your case, you can do this:
const path = require('path');
module.exports = {
resolve: {
alias: {
'#/components/Module1': path.join(
__dirname,
process.env.APP_BRAND
? '#/components/brands/' + process.env.APP_BRAND + 'Module1'
: '#/components/Module1')
}
},
};
Note that, here you would pass environment variable to your Webpack process (aka Node.js process) and make decision accordingly. Also, your resolve module must be absolute path.

How to create a library that can have individual components imported if required à la lodash

I've written a library in es6 using import/export. I can bundle this library using Rollup into a IIFE that can be used in the browser.
I also want to be able to use this library in other projects. However, I won't usually want to include the whole library, only parts of it.
Because the library is written using es6 import/export I can include the unbundled index.js file as a dependency in another project and then import { myFunc } from 'my-lib' and this works great - I only get myFunc when my project is bundled.
However, I ran into an issue because these files haven't been processed by babel and therefore contain es6 code such as arrow functions. I have read that if you're going to publish a library it shouldn't need to be transpiled by the end-user.
How can I take my es6 library and bundle it in such a way that it is transpiled, but also able to have its individual components imported? I want a similar situation to how lodash is organised.
This isn't the easiest thing to articulate so if anything isn't clear please leave a comment and I'll edit my question to try and clarify.
I have found it is best to use webpack to create 2 modules. One for web and one for node.
In your node build, you can choose to transpile, or simply keep the code in ES6, letting the library client decide.
Your web build can tree-shake and remove unused code.
Your webpack will export an array instead of an object.
Here is the official webpack snippet (https://webpack.js.org/concepts/targets/)
const path = require('path');
const serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
const clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];

css-loader can not resolve urls generated by file-loader

I have shared components npm package project which exposes components and css file.
Css file is built using webpack and url/file-loader to resolve fonts.
I can successfully build this project and resulting css look like:
#font-face {
font-family: 'MyFont';
src: url(cf871bb3514694d3252ee1d23f71dd6c.woff2);
}
The problem is when i try to use this css from another project which uses webpack and css-loader with css-modules turned on (modules:true), css-loader cannot resolve generated url:
Module not found: Error: Can't resolve 'cf871bb3514694d3252ee1d23f71dd6c.woff2'
If I change url to:
url(./cf871bb3514694d3252ee1d23f71dd6c.woff2);
Then it works.
Also if I set modules:false then everything works even without ./
So look like css-loader with turned on css-modules wants url paths as a node relative paths with ./, not just as a file name.
url in source file look like:
url('../assets/fonts/Myfont.woff2');
Is there a way how to solve it ?
It seems that you can't disable Node.js-style resolution in webpack in general. However, you can use the NormalModuleReplacementPlugin to intervene in module requests and resolve them on the fly like relative paths.
The following webpack config adjustments should do the trick:
const webpack = require('webpack')
const path = require('path')
module.exports = {
//...
plugins: [
// RegEx matches all requests neither starting with a dot nor a slash
new webpack.NormalModuleReplacementPlugin(/^[^./]/, resource => {
// Override the original request
resource.request = path.resolve(resource.context, resource.request)
})
]
};

standard way to deal with relative paths in node.js

Node.js path resolve system make me mad. I have
var a = '../data/data.db' path to a file but this path resolve differently depending from am I run this code from node.js directly or from test runner or from CI. Is there is a standard unified way how relative paths need to be managed in node.js?
You should use the __dirname property and path.resolve. __dirname gives you the path to the scripts location as opposed to the current execution location, and path.resolve cleans up the relative path.
var path = require('path');
var a = path.resolve(__dirname, '../data/data.db');
Your can use global variable such as __dirname which points to the directory the currently executing script resides.

nodeJS require.paths resolve problem

I am trying to require a file relatively and mysteriously the following is happening
This works well which points to /Users/marcos/Desktop/Taper/lib/utils.js
myPath = "/Users/marcos/Desktop/Taper/lib/./utils";
require(myPath);
This doesn't but it should point to exactly the same file:
require.paths.unshift("/Users/marcos/Desktop/Taper/lib")
require("./utils"); //Doesn't work with './'
require("utils"); //Works Fine
Anyone knows why I can't still use ./ in this case for loading the path since
require("path").resolve("/Users/marcos/Desktop/Taper/lib", "./utils")
results in:
"/Users/marcos/Desktop/Taper/lib/utils"
anyway?
Thanks in advance
UPDATED:
From the documentation:
A module prefixed with '/' is an absolute path to the file. For
example, require('/home/marco/foo.js') will load the file at
/home/marco/foo.js.
A module prefixed with './' is relative to the file calling require().
That is, circle.js must be in the same directory as foo.js for
require('./circle') to find it.
Without a leading '/' or './' to indicate a file, the module is either
a "core module" or is loaded from a node_modules folder.
If the given path does not exist, require() will throw an Error with
its code property set to 'MODULE_NOT_FOUND'.
Here’s the original answer, which refers to require.paths (which is no longer supported):
From the documentation:
In node, require.paths is an array of strings that represent paths to be searched for modules when they are not prefixed with '/', './', or '../'.
(emphasis mine)
You can pass that using NODE_PATH
Example:
NODE_PATH=`pwd` node app.js
I created a new node module called rekuire.
It allows you to "require" without using relative paths.
It's a big time saver when it comes to testing/refactoring.
https://npmjs.org/package/rekuire

Categories

Resources