Importing from lower level of node package - node.js

I have a node package (lets call it my-package) that currently looks like the following
src/
index.js
generators/
generate-stuff.js
index.js looks like the following
module.exports={
"some": {
"json": "objects",
...
},
"other": {
"json": "things",
...
}
};
Now I can import this file very easily in other packages by doing the following
const myPackage = require('my-package');
And myPackage will have the contents of that JSON you see above
However what I really want to import is a function that exists in generate-stuff.js (The structure of this package is inherited and I cannot easily change it)
That file looks like the following
module.exports = { functionIWantToExport(input1, input2){
return {
// do stuff with said inputs
};
}
}
However when I do something like
const functionIWant = require('my-package/generators/generate-stuff');
I get the following failure.
Cannot find module 'my-package/generators/generate-stuff'
Is there any way I can pull out the function I need from this package?
I can see in my node_modules that the module.exports is on the file I need

So was able to figure out the source of my problem. And it has do with the main in package.json
In my package.json there is the following
"main": "src/index.js"
So that's why just doing require('my-package'); works. But if I want to use the relative path it's still from above the src folder, so what I really need is
const functionIWant = require('my-package/src/generators/generate-stuff');
Doing this resolved my issue

Related

How to deal with node modules in the browser?

My title is a bit vague, here is what I'm trying to do:
I have a typescript npm package
I want it to be useable on both node and browser.
I'm building it using a simple tsc command (no bundling), in order to get proper typings
My module has 1 entry point, an index.ts file, which exposes (re-exports) everything.
Some functions in this module are meant to be used on node-only, so there are node imports in some files, like:
import { fileURLToPath } from 'url'
import { readFile } from 'fs/promises'
import { resolve } from 'path'
// ...
I would like to find a way to:
Not trip-up bundlers with this
Not force users of this package to add "hacks" to their bundler config, like mentioned here: Node cannot find module "fs" when using webpack
Throw sensible Errors in case they are trying to use node-only features
Use proper typings inside my module, utilizing #types/node in my code
My main problem is, that no matter what, I have to import or require the node-only modules, which breaks requirement 1 (trips up bundlers, or forces the user to add some polyfill).
The only way I found that's working, is what isomorphic packages use, which is to have 2 different entry points, and mark it in my package.json like so:
{
// The entry point for node modules
"main": "lib/index.node.js",
// The entry point for bundlers
"browser": "lib/index.browser.js",
// Common typings
"typings": "lib/index.browser.d.ts"
}
This is however very impractical, and forces me to do a lots of repetition, as I don't have 2 different versions of the package, just some code that should throw in the browser when used.
Is there a way to make something like this work?
// create safe-fs.ts locally and use it instead of the real "fs" module
import * as fs from 'fs'
function createModuleProxy(moduleName: string): any {
return new Proxy(
{},
{
get(target, property) {
return () => {
throw new Error(`Function "${String(property)}" from module "${moduleName}" should only be used on node.js`)
}
},
},
)
}
const isNode = typeof window === undefined && typeof process === 'object'
const safeFs: typeof fs = isNode ? fs : createModuleProxy('fs')
export default safeFs
As it stands, this trips up bundlers, as I'm still importing fs.

Resolving nodejs modules from other than node_modules location, is it possible?

For example, when I need a module located inside node_modules I can write const module = require('module') or import module from 'module'. If the module located in another location I need to write const module = require('../location/module'), etc...
This behavior not always desired, for example, If I have examples that I want a user will be able to copy/paste and run, and also to run this example from the project folder itself.
Can this be achieved?
I don't understand why I get downvoted, but I ended up to write the following wrapper which does the job:
const customRequire = function (moduleName, path) {
let module
try {
module = require(moduleName)
} catch (e) {
try {
module = require(`${path}/${moduleName}`)
} catch (e1) {
throw Error(`module ${moduleName} on path ${path} wasn't found`)
}
}
return module
}
Now if you copy/paste this code it will work. And if you will run it within the project where required modules defined and not located in node_modules it will work too.
The solution above work for the CommonJS module system, for ES6 you need to use the function import, to dynamically import modules
import('/modules/my-module.js')
.then((module) => {
// Do something with the module.
});

Nodejs - Create modules from string with the npm package 'module'

I want to load a custom module using the npm package 'module', to be able to require it like any other local folder that exports something.
I cannot find any documentation on the use of the package https://www.npmjs.com/package/module
I tried to use it without any documentation on it, but i simply cannot as i dont understand how you use it.
let myFunction = module.wrap(`module.exports = () => console.log("hej")`);```
Honestly, I've never read documentation about this but I've been using Node.js for years so I'll explain how I import modules. Given a project that has the files app.js and mod1.js and the subfolder utils with the file mod2.js, you could do something like this:
prj/mod1.js
module.exports = {
test: "testing"
}
prj/utils/mod2.js
module.exports = () => {
console.log("testing");
}
prj/app.js
var mod1 = require("mod1"),
mod2 = require("utils/mod2");
console.log(mod1.test);
mod2();
I think you are trying to create a module of your own to be able to use it at multiple places within your project ( correct me if I am wrong).
SO you can do that like :
In utils/custom.js
module.exports = {
logThis: "Hi"
}
OR
var a={
logThis:function (){
console.log('HI')
}
module.exports=a;
And then in the file where you want to access this :
In app.js
let custom=require('./utils/custom.js')

Rollup - incorrect path resolution

I want to use the library ebnf from NPM and create a bundle using rollup. Since the ebnf is installed to node_modules I also use the rollup plugin rollup-plugin-node-resolve.
The problem is that ebnf contains the code require('..') which - in my case - is resolved to dist in my case. Thus it seems .. is interpreted relative to the output file instead of being relative to the source file.
This is my rollup.config.js (taken from my test repo jneuendorf/rollup-broken-resolve):
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'cjs'
},
// name: 'MyModule',
plugins: [
resolve(),
commonjs(),
]
}
Is this a problem in rollup-plugin-node-resolve or am I doing something wrong?
Since some of the external libraries needed will still be available only as Common.js modules, you could also convert them to ES-Modules:
"Since most packages in your node_modules folder are probably legacy CommonJS rather than JavaScript modules, you may need to use rollup-plugin-commonjs"
https://github.com/rollup/rollup-plugin-commonjs
"Convert CommonJS modules to ES6, so they can be included in a Rollup bundle"
Just in case someone searching this issue on how to make #rollup/plugin-node-resolve (previously was rollup-plugin-node-resolve) to work with relative path. I just found the solution:
function resolve(file, origin) {
// Your way to resolve local include path
}
function pathResolve(options) {
return {
resolveId: function(file, origin) {
// Your local include path must either starts with `./` or `../`
if (file.startsWith('./') || file.startsWith('../')) {
// Return an absolute include path
return resolve(file, origin);
}
return null; // Continue to the next plugins!
}
};
}
Here is how to combine it with #rollup/plugin-node-resolve:
import {nodeResolve} from '#rollup/plugin-node-resolve';
function pathResolve(options) { /* ... */ }
export default {
// ...
plugins: [pathResolve(), nodeResolve()]
};

How do you setup a require.js config with typescript?

Ok, I've been reading a lot of questions and answers about this, and a lot of it is rubbish.
I have a very simple question. How do I do the equivalent of this:
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
require(['blah'], function(b) {
console.log(b);
});
In typescript?
This doesn't work:
declare var require;
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
import b = require('blah');
console.log(b);
s.ts(8,1): error TS2071: Unable to resolve external module ''blah''.
s.ts(8,1): error TS2072: Module cannot be aliased to a non-module type.
error TS5037: Cannot compile external modules unless the '--module' flag is provided.
Compiling with the --module flag, with a dummy blah.ts shim compiles, but the output is:
define(["require", "exports", 'blah'], function(require, exports, b) {
require.config({
paths: {
"blah": '/libs/blah/blah'
}
});
console.log(b);
});
Looks like it might work, but actually no, the require.config is inside the require block, it is set after it is already needed.
SO! I've ended up so far with this as a solution:
class RequireJS {
private _r:any = window['require'];
public config(config:any):void {
this._r['config'](config);
}
public require(reqs:string[], callback:any):void {
this._r(reqs, callback);
}
}
var rjs = new RequireJS();
rjs.config({
paths: {
"jquery": '/libs/jquery/jquery',
"slider": '/js/blah/slider'
}
});
rjs.require(['slider'], function(slider) {
console.log(slider);
});
Which seems terrible.
So be clear, inside modules that depend on each other, this sort of thing works perfectly fine:
import $ = require('jquery');
export module blah {
...
}
I just need a proper way to setting the requirejs config at a top level, so that the imported paths for the various named modules are correct.
(...and this is because, largely, 3rd party dependencies are resolved using bower, and installed in the /lib/blah, where as the shim files I have for their definitions are in src/deps/blah.d.ts, so the default import paths are incorrect after moving the generated modules files into /js/ on the site)
NB. I've mentioned jquery here, but the problem is not that jquery doesn't work property as an AMD module; I have a shim jquery.ts.d file for this. The issue here is the requirejs paths.
Yesterday I wrote up a solution to this exact issue on my blog - http://edcourtenay.co.uk/musings-of-an-idiot/2014/11/26/typescript-requirejs-and-jquery:
TL;DR - create a config file config.ts that looks something like:
requirejs.config({
paths: {
"jquery": "Scripts/jquery-2.1.1"
}
});
require(["app"]);
and ensure your RequireJS entry point points to the new config file:
<script src="Scripts/require.js" data-main="config"></script>
You can now use the $ namespace from within your TypeScript files by simply using
import $ = require("jquery")
Hope this helps
This post is 3 years old, and there's a lot of changes that have been made when using Typescript. Anyway, after some search on the web,some research on TS documentation-these guys made some good job, I found something that could help.
so this can apply to the latest current of TS (2.2.1)
you probably know that you can use
npm install --save #types/jquery
do the same for your 3rd party JS librairies such as require
now you need to define what your TypeScript Compiler has to do, and how, so create a tsconfig.json file that contains:
// tsconfig.json file
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "./Scripts",//Same as require.config
"module": "amd",
"moduleResolution": "Node",//to consider node_modules/#types folders
"noImplicitAny": false,
"target": "es5", // or whatever you want
"watch": true
}
now, let's focus on require's configuration
// require-config.ts
declare var require: any;
require.config({
baseUrl: "./Scripts/",
paths: {
"jquery": "vendor/jquery/jquery.x.y.z"
// add here all the paths your want
// personnally, I just add all the 3rd party JS librairies I used
// do not forget the shims with dependencies if needed...
}
});
so far so good
now focus on your module written in TS that would use jquery and that is located in Scripts/Module folder:
// module/blah.ts
/// <amd-module name="module/blah" />
import $ = require("jquery");
export function doSomething(){
console.log("jQuery version :", $.version);
}
So this answer looks the same as Ed Courtenay's, doesn't it?
and user210757 mentioned that it does NOT work!!!
and it does not! if you type in your console tsc -w --traceResolution, you'll see that tsc cannot find any definition for jquery.
Here's how to alleviate assuming you previously launch npm install --save #types/jquery by doing this, in a folder named node_modules\#types, you should get the TS definition for jquery
select the package.json file in jquery subfolder
look for the "main" property
set it to "jquery", the same as the alias you are using in your require.config
and done! your module would be transpiled as
define("module/blah", ["require", "exports", "jquery"], function (require, exports, $) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function doSomething() {
console.log("jQuery version:", $.fn.jQuery);
}
exports.doSomething = doSomething;
});
and that JS code looks good to me!
I just don't like the fact that our module dependencies list "require" & "exports", that sounds like a TS issue, but anyway IT WORKS!
if you want to use import for javascript modules you need to tell typescript about it so,
declare var require;
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
// Important, place in an external.d.ts:
declare module 'blah'{
// your opportunity to give typescript more information about this js file
// for now just stick with "any"
var mainobj:any;
export = mainobj;
}
import b = require('blah');
console.log(b);
alternatively you could simply do:
var b = require('blah'); and it should work as well

Resources