How to use dynamic import from a dependency in Node.js? - javascript

I'm using Node.js (v16) dynamic imports in a project to load plugins using a function loadJsPlugin shown here:
import { pathToFileURL } from 'url';
async function loadJsPlugin(pluginPath) {
const pluginURL = pathToFileURL(pluginPath).toString();
const result = await import(pluginURL);
return result.default;
}
My main program provides absolute paths to the loadJsPlugin function, such as /home/sparky/example/plugins/plugin1.js (Linux) or C:\Users\sparky\example\plugins\plugin1.js (Windows). The pathToFileURL function then converts these absolute paths to URLs like file:///home/sparky/example/plugins/plugin1.js (Linux) or file:///C:/Users/sparky/example/plugins/plugin1.js (Windows).
Loading the plugins this way works fine when the loadJsPlugin function is in the same package as the main program, like this:
import { loadJsPlugin } from './plugin-loader.js';
async function doSomething() {
const plugin = await loadJsPlugin('...'); // works
// use plugin
}
However, if I try to move loadJsPlugin to a separate library and use it from there, it fails with Error: Cannot find module '<url here>'
import { loadJsPlugin } from '#example/plugin-loader';
async function doSomething() {
const plugin = await loadJsPlugin('...'); // error
// use plugin
}
NOTE: the dependency name here is not on NPM, it's on a private repository and there's no problem loading the dependency itself. Also, static ES6 imports in general are working fine in this system.
I looked through Node.js documentation, MDN documentation, and other StackOverflow questions for information about what is allowed or not, or whether dynamic import works differently when in the same package or a dependency, and didn't find anything about this. As far as I can tell, if a relative path or file URL is provided, and the file is found, it should work.
Ruling out file not found:
I can switch back and forth between the two import lines to load the loadJsPlugin function from either ./plugin-loader.js or #example/plugin-loader, give it the same input, and the one in the same package works while the one from the dependency doesn't.
When I test in VS Code, I can hover the mouse over the URL in the Error: Cannot find module 'file:///...' message and the file opens just fine
I can also copy the 'file:///...' URL to a curl command (Linux) or paste it into the address bar of Windows Explorer and it works.
If I try a path that actually doesn't exist, I get a slightly different message Error [ERR_MODULE_NOT_FOUND]: Cannot find module '<path here>', and it shows the absolute path to the file that wasn't found instead of the file URL I provided.
Checking different file locations:
I tried loading plugins that are located in a directory outside the program (the paths shown above like /home/sparky/example/plugins/...); got the results described above
I tried loading plugins that are located in the same directory (or subdirectory) as the main program; same result
I tried loading plugins that are packaged with the dependency in node_modules/#example/plugin-loader; same result (obviously this is not a useful set up but I just wanted to check it)
I'd like to put the plugin loader in a separate library instead of having the same code in every project, but it seems that dynamic import only works from the main package and not from its dependencies.
I'm hoping someone here can explain what is going on, or give me a pointer to what might make this work.

Related

Failed to resolve module specifier

I`m only starting my JS journey and I will be really grateful if you help me to receive data using the JS. I found that info on the alcor exchange site that is the exchange site for wax (gaming crypto currency).
What is on site:
// Code not tested yet, and provided for explanation reason
import fetch from 'node-fetch'
import { Api, JsonRpc, RpcError } from 'eosjs'
const rpc = new JsonRpc('https://wax.greymass.com', { fetch })
// Get buy orderbook from table
const { rows } = await rpc.get_table_rows({
code: 'alcordexmain',
table: 'buyorder',
limit: 1000,
scope: 29, // Market id from /api/markets
key_type: 'i128', // we are using it for getting order sorted by price.
index_position: 2
})
I faced with some trouble because of JSHint version and updated it to 9. But still "await" is red and JSHint is asking for semicolon after it - which causes huge amount of new errors. However the project is opening in the browser with no info of course. But in the console I see an error.
index.html:1 Uncaught TypeError: Failed to resolve module specifier "node-fetch". Relative references must start with either "/", "./", or "../".
P.S. I checked the posts with such error but actually didn't understand what should I do because all of them are proposing some changes for JSON file and I only have index.html and that js. file.
There is a difference between JavaScript in your browser and JavaScript on a server.
JavaScript in a browser is the code that can be injected into HTML (inlined or linked), which is evaluated by the browser.
JavaScript on a server is not related to JavaScript in a browser. The language is the same, but the environment is not. It's like “draw in Paint” and “draw on a real life canvas”. Colors are the same, but everything else is not.
So, what you are trying to do here is to run server-side JavaScript in a browser. That's the real reason you're getting this error.
There are two ways to fix this error.
Probably the one you should go
You should install Node.js, read about npm, init your npm project, put everything into .js file and eval using Node.
In a nutshell, let's say you've already installed Node.js and node -v outputs something in your terminal. Then everything you need to do is:
$ cd path/to/the/directory/you/like
$ npm init -f
$ npm install --save node-fetch eosjs
$ touch index.js
Then edit index.js using your favorite editor, adding there the code you're trying to run.
You may encounter error due to using await on a “top-level”. In this case, put it into an async function:
import fetch from 'node-fetch'
import { Api, JsonRpc, RpcError } from 'eosjs'
const rpc = new JsonRpc('https://wax.greymass.com', { fetch })
(async () => {
const { rows } = await rpc.get_table_rows({
code: 'alcordexmain',
table: 'buyorder',
limit: 1000,
scope: 29, // Market id from /api/markets
key_type: 'i128', // we are using it for getting order sorted by price.
index_position: 2
});
})();
Aaaand, that's it. You do not need to run browser here at all.
Probably the one you should not go, but can
If you need to run your JavaScript in a browser, then you need to either bundle all the deps using a tool like webpack (which still requires you to use Node.js & npm), or you may replace the deps you're trying to use with client-side alternatives.
E.g. instead of requiring node-fetch you may use Fetch API.
What to use instead of eosjs I do not know, but if you decide to use this dependency in a browser, you will at least need to use import maps to tell the browser how to resolve such dependencies. Because, well, the error you've got tells you exactly this: your browser does not know what you're trying to import. Browsers use URLs as import paths, not ids (or “bare names”).

InternalError Metro has encountered an error: While trying to resolve module `child_process`

I am trying to call a function in a python file from a js file, I got this to work through my console, but I am now trying to implement it in a mobile app using expo.
The way I had set this up is, I have the JS file for a certain screen in my app, this then calls a function in a separate JS file, which then calls the function in the python file.
I am using the child_process module to talk to python from JS.
And as I said, this was working before I tried to export the JS function to my screen file.
index.js
export function foo(process, sentence){
const spawn = require("child_process").spawn;
const process = spawn("python3", ["./python.py", sentence]);
...
}
screen.js
*other imports
import { foo } from "./filepath..."
...
But when I run npm start I get the following error:
Failed building JavaScript bundle.
While trying to resolve module `child_process` from file `/Users/mee/Documents/GitHub/project/app/screens/screen.js`, the package `/Users/mee/Documents/GitHub/project/node_modules/child_process/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`/Users/me/Documents/GitHub/project/node_modules/child_process/app/screens/screen.js`. Indeed, none of these files exist:
How can I fix this?
It won't work for few reasons
child_process is part of the node standard library, it's not available in other environments like react-native or browser
even if above was not true, there is no python3 executable on your phone
python.py file from your local directory wouldn't be even uploaded to the phone because bundler is only uploading one big js file with entire js code combined + assets, python.py is neither of those.
Only solution that make sense it to rewrite that code to javascript.
Technically it's not impossible, there might be a way to do that, by compiling python interpreter for mobile platform, or using some tool that translates python code into js, but it's not something that you should consider.

Single API to load JSON file in Browser and NodeJS?

Is there an existing API or library that can be used to load a JSON file in both the browser and Node?
I'm working on a module that I intend to run both from the command-line in NodeJS, and through the browser. I'm using the latest language features common to both (and don't need to support older browsers), including class keywords and the ES6 import syntax. The class in question needs to load a series of JSON files (where the first file identifies others that need to be loaded), and my preference is to access them as-is (they are externally defined and shared with other tools).
The "import" command looks like it might work for the first JSON file, except that I don't see a way of using that to load a series of files (determined by the first file) into variables.
One option is to pass in a helper function to the class for loading files, which the root script would populate as appropriate for NodeJS or the browser.
Alternatively, my current leading idea, but still not ideal in my mind, is to define a separate module with a "async function loadFile(fn)" function that can be imported, and set the paths such that a different version of that file loads for browser vs NodeJS.
This seems like something that should have a native option, or that somebody else would have already written a module for, but I've yet to find either.
For node, install the node-fetch module from npm.
Note that browser fetch can't talk directly to your filesystem -- it requires an HTTP server on the other side of the request. Node can talk to your filesystem, as well as making HTTP calls to servers.
It sounds like as of now, there is no perfect solution here. The 'fetch' API is the most promising, but only if Node implements it some day.
In the meantime I've settled for a simple solution that works seamlessly with minimal dependencies, requiring only a little magic with my ExpressJS server paths to point the served web instance to a different version of utils.js.
Note: To use the ES-style import syntax for includes in NodeJS (v14+) you must set "type":"module" in your package.json. See https://nodejs.org/api/esm.html#esm_package_json_type_field for details. This is necessary for true shared code bases.
Module Using it (NodeJS + Browser running the same file):
import * as utils from "../utils.js";
...
var data = await utils.loadJSON(filename);
...
utils.js for browser:
async function loadJSON(fn) {
return $.getJSON(fn); // Only because I'm using another JQuery-dependent lib
/* Or natively something like
let response = await fetch(fn);
return response.json();
*/
}
export { loadJSON };
utils.js for nodeJS
import * as fs from 'fs';
async function loadJSON(fn) {
return JSON.parse(await fs.promises.readFile(fn));
}
export { loadJSON };

How do you get webpack to *actually* ignore an external and rely on the browser to import?

I'm trying to get webpack to ignore an import, so that its imported by the browser using a native ES6 import statement, not webpack. I'm trying to get ffmpeg.js to import directly as it crashes webpack when it tries to bundle it, as the files are too large.
Following the answer here (How to exclude a module from webpack, and instead import it using es6), I have my code in the local tree as /ffmpeg/ffmpeg-mpeg.js and verified my dev server can access as http://localhost:8080/ffmpeg/ffmpeg-webm.js
I then import via:
import ffmpeg from '/ffmpeg/ffmpeg-webm.js';
And add that to the externals section of my webpack config:
externals: {
'/ffmpeg/ffmpeg-webm.js': 'ffmpeg',
},
The result is an link that looks like this
webpack:///external "ffmpeg"
containing:
module.exports = ffmpeg;
Which then fails with "Uncaught Error: Cannot find module ?" (In fact that error is hardcoded in the generated file)
So that seems to assume there is a global ffmpeg option and then maps that module to that, but instead I want it leave the line completely untouched by webpack and leave it to the browser.
Whats the correct way to do that? The exclude rule thats downvoted on that page doesn't work either.
Edit:
You can use this:
import(/* webpackIgnore: true */'/ffmpeg/ffmpeg-webm.js').then(({default: ffmpeg}) => {
//Do what you want to do with ffmpeg
});
Which will prevent webpack from compiling the import (so it will be a regular ES6 import)
Original answer:
You forgot to include the external script in your page.
Also since you pointed out that your file is very big, I'd recommend to include it defered
So you need to add
<script src="/ffmpeg/ffmpeg-webm.js" defer></script>
To the head of your app and you would then import it slightly differently using the import function with a callback
import('/ffmpeg/ffmpeg-webm.js').then(ffmpeg => {
//Do what you want to do with ffmpeg
});
Small note: the externals key does not need to be the path of your file, it's just the name you will use when importing, so rename it if you are getting confused with the path
module.export = {
//...
externals: {
"ffmpeg-webm": "ffmpeg"
}
}
//Then import
import('ffmpeg-webm').then(ffmpeg => {
//Do what you want to do with ffmpeg
});
Alternatively for node js, instead of using externals you could use
const ffmpeg = __non_webpack_require__('/ffmpeg/ffmpeg-webm.js')
Just keep in mind that this will transform it as a normal require that only works with node js

PapaParse Script Path is Undefined

I've been looking all over for a solution to this and I can't figure it out for the life of me...
The sitch: I have a lazy loaded React component which is supposed to parse a CSV file (with PapaParse) which is all built within the create-react-app framework. But for some reason, despite everything saying it should work, when I try to use PapaParse, I get this error:
Error: Script path cannot be determined automatically when Papa Parse is loaded asynchronously. You need to set Papa.SCRIPT_PATH manually.
But since this is bundled with Webpack I have no idea what this script path should be and I've tried setting the path to the PapaParse folder within the project folder structure (i.e. something like ../../node_modules/papaparse) to no avail. I actually got a different error when I put in a path:
papaparse?papaworker:1 Uncaught SyntaxError: Unexpected token <
For some more context, the component in question looks a little like this:
import Papa from 'papaparse';
class Dialog extends React.Component {
...
handleFileChange = () => {
...
Papa.parse(file, config);
...
}
...
}
I installed PapaParse via npm, so it should be the latest version, some things go back to 2014-15 where these problems existed, but it's said to have been updated...
Actually, I'm confused, because it works well on both of my projects, hence, React Web App and React Native iOS, Android App.
I imported it like below:
import Papa from 'papaparse/papaparse.min';
Surely, for each project, there are some configs that maybe some libraries work badly. but you maybe can use Node.js path for setting SCRIPT_PATH for Papa:
import path from 'path';
...
Papa.SCRIPT_PATH = path.resolve('/node_modules/papaparse');
...
Or if it has the issue too, then use this link answer for using papaparse directly in your code. that is not recommended.

Categories