module-alias
module-alias copied to clipboard
IMPORTANT: DO NOT USE! Use import mapping instead
A similar feature to this library is already available in node as a standard: import mapping.
Not only does it allow for directory mapping, but it also allows dependency aliasing, and works for both require
and import
(ESM) WITHOUT breaking resolution behaviour in production or other libraries like using this library does.
You have been warned.
I know this sounds hateful, however, that is not my intention. My intention is instead to spread awareness about this mostly undocumented feature (it is not shown in any package manifest documentation besides node's, and it is relatively tucked away) so people can write better software without needing to use hacky libraries like this one and without adding unnecessary dependencies.
@Nytelife26 , thanks for raising awareness. This was introduced "recently", so on older versions of node, you might still want module-alias
- therefore we probably won't just "deprecate" the package.
Would you mind opening a PR to update the README of this package, explaining that import mapping exists and should be used instead?
Yes, that is fine. I would be more than happy to open a pull request regarding versioning and use of import mapping where possible. Thank you for the response.
Further reading here and with a great example of directory mapping here. Hopefully this latter example clears up any confusion about the difference between "imports"
and "exports"
usage too
@nick-bull Thanks for the extra examples :) greatly appreciated
An interest afterthought to replacing this module is that import mapping does not resolve directory imports, e.g. "#services/*": "./src/services/*.js"
would not resolve import ... from '#services/someCoolService'
to ./src/services/someCoolService/index.js
.
You can get around this with "#services/*": "./src/services/*/index.js"
but then that obviously doesn't work with non-index.js
mappings.
The value is not really a regex, so something like "./src/services/*(/index)?.js"
would not work. Directory-style imports with:
"#services/*": "./src/services/*.js"
import ... from '#services/someCoolService'
throws Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import ... is not supported resolving ES modules imported from ...
Anybody know a way around this?
@nick-bull ..uh
"#services/*": "./src/services/*.js"
Of course it doesn't? That'd resolve to ./src/services/someCoolService.js
Use this instead:
"#services/*": "./src/services/*"
@nick-bull ..uh
"#services/*": "./src/services/*.js"
Of course it doesn't? That'd resolve to
./src/services/someCoolService.js
Use this instead:"#services/*": "./src/services/*"
I have already suggested that in my comment; I've edited my response so it's a little clearer, as it was a bit densely packed, as it does not work, it throws an error
@nick-bull No, you did not already suggest what I said specifically. You suggested something similar, which was "#services/*": "./src/services/*.js"
as aforementioned. Although, actually, that still works - you'll notice if you import #services/someCoolService/test
, that does in fact resolve to ./services/someCoolService/test.js
, for both require and import. And then if you do the other thing I suggested, "#services/*": "./src/services/*"
, you have to import #services/someCoolService/test.js
to achieve the same result, but it will definitely work.
I hope that helps.
@nick-bull No, you did not already suggest what I said specifically. You suggested something similar, which was
"#services/*": "./src/services/*.js"
as aforementioned. Although, actually, that still works - you'll notice if you import#services/someCoolService/test
, that does in fact resolve to./services/someCoolService/test.js
, for both require and import. And then if you do the other thing I suggested,"#services/*": "./src/services/*"
, you have to import#services/someCoolService/test.js
to achieve the same result, but it will definitely work.I hope that helps.
You're right, I totally thought I'd written that example too and forgot to include it. I'm still not sure that answers my question though - is there a way to write a mapping that will allow both of the following:
import ... from '#coolService' // coolService/index.js
import ... from '#coolService/someFile.js' // coolService/someFile.js
Edit, scrap the above. It was because I had "type": "module"
in package.json
in my test project, causing it to throw the Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import ... is not supported resolving ES modules imported from ..
I'd mentioned before. You'll either need transpilation or node --experimental-specifier-resolution=node ...
. Thanks Nytelife!
No worries @nick-bull. My answer if not for your discovery of needing to set the type to modules would've been to just import #coolService/index.js
if it wouldn't work the conventional way anyways, but I'm glad you found your problem.
If you have any further questions or concerns let me know.
This package didn't work for me no matter how I hard I tried to configure it. Maybe because I use ES6+ including imports (no single require() in my project) Builtin functionality works.
"imports": {
"##/*": "./*"
},
jsconfig
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"##/*": ["./*"]
}
},
"exclude": ["node_modules"]
}
usage
import { Model } from "##/api/models.js";
@thefoxie thank you for the contribution :) however, this is not a place to report the package failing. I am glad the built-in functionality works, however, if you have any problems you should still create a separate issue so the ilearnio team can ensure the package works as intended for those who need it.
After reading the documentation, I still don't understand how to use it... Could you please explain to me how can I convert this:
"_moduleAliases": {
"@root": ".",
"@submodules": "submodules",
"@db": "src/db",
"@middleware": "src/middleware"
}
to use native import mapping instead? Preferably without changing any other code. Thanks.
@Papooch you will have to convert to using #alias
instead of @alias
, but that's it.
"imports": {
"#root/*": "./*",
"#submodules/*": "./submodules/*",
"#db/*": "./src/db/*",
"#middleware/*": "./src/middleware/*"
}
I hope that helps.
Well that's what I tried also, but I am getting and error.
in C:\repos\project\src\db\connect.js
, I am using
const { initModels } = require('#submodules/db-models/master');
But it throws an error, which I don't understand:
Error: Cannot find module 'C:\repos\project\submodules\db-models\master'
at Object.<anonymous> (C:\repos\project\src\db\connect.js)
But the folder C:\repos\project\submodules\db-models\master
DOES exist and has an index.js
file in it which exports the initModels function.
Does folder mapping work differently to file mapping using this technique?
@Papooch You didn't happen to get burned in the same way as I did? Test by using
const { initModels } = require('#submodules/db-models/master/index.js');
@nick-bull Well, that does seem to work, but I have to explicitly state the file, including the file extension. require('#submodules/db-models/master/index')
does not work. I would have to change imports in the whole project and lose the flexibility along the way.
I would very much prefer to use a native solution instead of a library, but not in a way that makes my experience worse.
@Papooch I suspect you're being burned for the same reason as the prior mentioned comment, this feature isn't going to affect extension resolution. Try node --es-module-specifier-resolution=node
, or install esm
and use node -r esm
. Report back and let us know if that works!
I've gotten this to work as described above.
In my package.json
"imports": {
"#app/*": "./dist/app/*",
"#lib/*": "./dist/lib/*",
"#src/*": "./dist/*"
},
then using node --es-module-specifier-resolution=node
after compilation.
Where I am having issues, is when I try and use jest. Previously when using module-alias I could do the following in jest.config.ts
moduleNameMapper: {
'^#app/(.*)$': '<rootDir>/src/app/$1',
'^#lib/(.*)$': '<rootDir>/src/lib/$1',
'^#src/(.*)$': '<rootDir>/src/$1'
},
but when I try and run tests, now, I am getting:
Configuration error:
Could not locate module #app/index.js mapped as:
/Users/blah/src/app/$1.
Please check your configuration for these entries:
{
"moduleNameMapper": {
"/^#app\/(.*)$/": "/Users/blah/src/app/$1"
},
"resolver": undefined
}
> 1 | import { FastifyApp } from '#app/index.js';
It looks like it is looking for #app/index.js
instead of #app/index.ts
If I do a quick hardcode hack, the test runs (I hardcode index.ts):
moduleNameMapper: {
'^#app/(.*)$': '<rootDir>/src/app/index.ts',
'^#lib/(.*)$': '<rootDir>/src/lib/$1',
'^#src/(.*)$': '<rootDir>/src/$1'
},
...any ideas?
@Papooch if you wish to use extensions in the map to avoid having to specify .js
you can. Although, as people have mentioned, it is better to set the module resolution to use node's.
@initplatform how are the imports specified in the actual files?
Ha! I was banging my head against the monitor for a good hour earlier before I wrote that post. 5 minutes after trying to respond to your question I found it. Thanks!
For whatever reason, when I was debugging the transition over to import mapping I had done this:
import { FastifyApp } from '#app/index.js';
instead of this:
import { FastifyApp } from '#app/index';
Can't even remember what I was testing... but I forgot to remove the .js
after whatever I was doing.
Building and running worked fine, but jest exposed the error.
Sometimes it's the tiny things right :)
I am excited to see this import mapping land in node. At first I was averse to the #
instead of using the @
, but I kinda like it now. I am assuming it's to differentiate between npm namespaces and actual mappings...
Thanks to all involved that created module-alias
and for your response @Nytelife26
@initplatform I'm not sure you'll need the index
either, as I'm pretty sure directory imports are part of --es-module-specifier-resolution=node
No worries @initplatform. I give special credit to @nick-bull for the module resolution override because prior to that I was unaware it even existed honestly. I'm glad you found your solution, though!
@initplatform I'm not sure you'll need the
index
either, as I'm pretty sure directory imports are part of--es-module-specifier-resolution=node
I believe that's an annoyance with typescript.
For whatever reason, if only using the typescript path alias, I need to specify index or typescript won't compile. I don't need to use index for nested routes though.


One other reason to keep maintaining this package is that import mapping only works for "type": "module"
, which isn't ideal for e.g., React Native
One other reason to keep maintaining this package is that import mapping only works for
"type": "module"
, which isn't ideal for e.g., React Native
Is that so? Standard React and JSX relies on ESM, I cannot see why React Native wouldn't permit it.
I've discovered that I might need to switch to es6 modules, and that module-alias seems to be not working. I stumbled across this thread and converted my project over to using the "imports" property in package.json, but I'm getting an error I can't solve:
Package.json:
"imports": {
"#Common/*": "../Common/*.js",
"#Typescript/*": "../Typescript/*.js"
}
Error:
Invalid "imports" target "../Common/*.js" defined for '#Common/*' in the package config [PathToRepo]\Server\package.json imported from [PathToRepo]\Server\server.js
Does this mean that the inbuilt "imports" property doesn't work outside the directory that the package.json is located within? Is there a work around for this? If not is there a way to make module-alias work with ES6 modules?
Does this mean that the inbuilt "imports" property doesn't work outside the directory that the package.json is located within?
Do I get to ask why you're trying to use import paths to import from
directories not contained within your project? The paths you're aliasing
should always be contained within the project that the package.json
is for.
It appears that you're working on a project with multiple subpackages.
If that is the case, use the import paths in the package.json
for the
top level directory of the project.
I hope this helps.
I can't figureout how to get node 16 native ESM support to work with Mocha 8 and module-alias... So this is my only option
Error on node 16, mocha 8, type: "module"
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@src/common' imported from /src/mocking/reset.js
at packageResolve (internal/modules/esm/resolve.js:655:9)
at moduleResolve (internal/modules/esm/resolve.js:696:18)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
at Loader.resolve (internal/modules/esm/loader.js:86:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:56:40)
at link (internal/modules/esm/module_job.js:55:36)
mocha test wrapper
import moduleAlias from 'module-alias'
moduleAlias()
import test from '../app.test.js'
export default test
package.json
"_moduleAliases": {
"@src": "./src"
},
reset.js
import { simple as logger } from '@src/common/Logger.js'
@Nytelife26 because I have the following directory structure:
- Client
- Common
- Server
Client and server both include common, so it can't be "within" either project.
Also WHY do you need to enforce a very arbitrary condition of "you can not alias any code outside of the directory of your project'? Shared node_modules folders, shared code and external libraries being outside of your codebase (for the purposes of cleaner commits to git repositories and such) are not uncommon occurances in the wild.
My top level doesn't have a package.json (because it's not a package ...?). Having package.jsons that exist alone with no actual code associated with them seems like a bad design choice.
Instead I hacked an experimental node API to allow accessing paths outside of the current directory.