prevent importing non existing node modules - javascript

I'm using React Native, npm, node v 8.9.4, eslint.
If I import View from 'View', my app simply takes View from 'react-native' and works (like it does when I use the proper form import { View } from 'react-native'). But in production (where different versions of node and different build processes may exist), it crashes on this.
I rather have it throw an error right away if I try to import something in this way, rather than some system trying to be smart about it. I want to have either node, eslint, or some other system warn me about this kind of import.
How can I make sure that importing from a non-existing node module simply throws an error, instead of trying to be smart about it?

Related

Circular dependency (?) in library & nodejs library code - Object prototype may only be an Object or null: undefined

I'm using filestack-js in a Rails project which is bundled with Vite. Everything works as expected until I include the ESM module for the filestack-js library, in this case in a StimulusJS controller:
import { Controller } from "stimulus";
import * as filestack from "filestack-js";
export default class extends Controller {
// some irrelevant implementation code that calls filestack.init(...)
}
Loading the above controller file in the browser causes an error:
tslib.es6.js:25 Uncaught TypeError: Object prototype may only be an Object or null: undefined
at setPrototypeOf (<anonymous>)
at __extends (tslib.es6.js:25)
at http.ts:43
at node_modules/filestack-js/build/module/lib/request/adapters/http.js (http.ts:64)
at __init (chunk-IHTDASF6.js?v=1616a449:14)
at request_adapter.node.ts:17
This is an error produced by the browser while working in a development environment, using Vite to build and serve ES modules to the browser directly. It handles Typescript compilation. Removing the import * as filestack bit makes the error go away (but obviously breaks the class' functionality).
My google searches seem to suggest that this might be a circular dependency problem. The browser stack trace points towards a file in the filestack-js library:
// src/lib/request/adapters/http.ts
import * as url from 'url';
import * as zlib from 'zlib';
import Debug from 'debug';
import { AdapterInterface } from './interface';
import { getVersion } from '../../utils';
import * as Stream from 'stream'; // <---------- Stream imported here
import { FsRequestOptions, FsResponse } from '../types';
import * as utils from '../utils';
import { prepareData, parseResponse, combineURL, set as setHeader, normalizeHeaders } from './../helpers';
import { FsRequestErrorCode, FsRequestError } from '../error';
import { FsHttpMethod } from './../types';
const HTTPS_REGEXP = /https:?/;
const HTTP_CHUNK_SIZE = 16 * 1024;
const MAX_REDIRECTS = 10;
const CANCEL_CLEAR = `FsCleanMemory`;
const debug = Debug('fs:request:http');
class HttpWritableStream extends Stream.Writable {
// omitted class definition
}
Where Stream.Writable is actually undefined due to a circular dependency problem. I have no idea how that would happen or seem to only affect me.
This is not an issue that has been reported on the filestack-js issue tracker.
Debugging in the browser and cloning/linking the repository locally have confirmed that Stream.Writable is returning undefined, but I don't know enough about JS to understand why. Supposedly this typically happens due to a circular dependency, but I'm not sure how the nodejs Stream module would have circular dependencies on a random library like filestack-js. I am also inexperienced enough in the JS world to understand exactly what it means to be using a nodeJS library like Stream in a browser module - filestack-js has both browser modules and commonJS/nodeJS modules so I'm not sure how/if they relate or interact.
Here's what the Stream object looks like when logged to a browser console. Clearly something has been imported but Writable is not a property of what was imported:
FWIW this happens on Chrome and Firefox, latest versions of each.
I also tried using dpdm to analyze the filestack-js project for circular dependencies. It did find some but it doesn't appear as if they are causing errors, and it does seem to explicitly be excluding node libraries and other dependency libraries.
Ok I think I've solved my issue but I'm not an expert so I'll try to explain what the problem was. Feel free to chime in with clarification if you know better.
This was caused by filestack-js's heavy usage of nodejs libraries. Historically, Webpack v4 has polyfilled a lot of common NodeJS libraries for usage in-browser, entirely transparent to most developers. This worked great but was complete magic.
Rollup, and incidentally, Webpack v5, do not do this polyfilling, so any nodeJS libraries used by "ESM" libraries from NPM that aren't directly compatible with modern browsers will just break. In order to polyfill this manually I had to instruct Vite & Rollup to alias the name of the nodejs stream module to something that is directly compatible with browsers, and install that. To do that, I:
yarn add --dev stream-browserify
And added the following to my vite.config.js:
// ...
resolve: {
alias: {
stream: "stream-browserify",
},
},
// ...
There should be a very similar (but different) way of telling Rollup to do this, because here I do it though the Vite configuration.
For extra context, here is the GitHub issue I opened on the filestack-js repo: https://github.com/filestack/filestack-js/issues/458
Imported it directly as recommended in the link Taylor recommended.
import * as filestack from 'filestack-js/build/browser/filestack.esm';
https://github.com/filestack/filestack-js/issues/458#issuecomment-927373100

'ActionSheetIOS' is not exported from 'react-native-web/dist/index'

How to avoid the this kind of warning?
My app is working but it gives warning in terminal like below
Compiled with warnings.
/home/karim/Desktop/React-Native/RN-Complete-Guide/node_modules/react-navigation-header-
buttons/src/overflowMenuPressHandlers.js
Attempted import error: 'ActionSheetIOS' is not exported from 'react-native-web/dist/index'.
It is because of of I imported HeaderButtons and HeaderButton from react-navigation-header-buttons like below
import { HeaderButtons } from "react-navigation-header-buttons";
import { HeaderButton } from "react-navigation-header-buttons";
And I am using react-native 4.x veriosn. is there any way to avoid this warning?
This file in the source for react-navigation-header-buttons imports ActionSheetIOS, which is not supported by React Native web applications. This seems like it's probably an issue with React Native web itself, as even importing the ActionSheetIOS component in your project without actually using it will cause a crash from my past experience.
You probably won't be able to use react-navigation-header-buttons for your solution because of this issue. The authors also go on to say in their README that web support is currently experimental:
Supports iOS and Android, web support is experimental.
I'd suggesting either finding a way to avoid this library or flag an issue on the repo to suggest swapping ActionSheetIOS in with a cross-platform action sheet solution like react-native-action-sheet.
in react-native-web/dist/index.js i exported
export { ActionSheetIOS }; and somehow the warning disappeared

Flow complains about untyped import

I'm starting out on a React Native project that uses react-native-geolocation-service to get the user's GPS location, but Flow is complaining that the library is untyped. The library has Typescript types, but doesn't seem to have Flow types, and VS Code gives me this error when I import the library.
Importing from an untyped module makes it any and is not safe! Did you mean to add // #flow to the top of react-native-geolocation-service? (untyped-import)Flow(LintError-untyped-import)
Can I convince Flow to stop complaining about this library, or should I switch to Typescript? I don't have any particular preference for Flow, it's just what react-native init set up for me.
I used this to create the project:
npx react-native init WarmerWalker
Then I added the library like this:
npm install react-native-geolocation-service
I import the library like this in my App.js:
import Geolocation from 'react-native-geolocation-service';
I tried adding the library to the untyped section in .flowconfig, but that didn't help. Neither did the ignore section.
The code seems to work, it's just making Flow complain.
Thanks to a comment from Alex, I learned about strict mode. The sample app that react-native created used #flow strict-local, so that was causing the problem. Removing strict-local made it stop complaining.
However, since the library I want to use has Typescript type definitions, I'm going to switch to Typescript. I regenerated the sample app with the Typescript template, like this:
npx react-native init MyApp --template react-native-template-typescript

Tree-shakeable strategy pattern in Typescript

I have been trying to build a library that requires its consumers to use a specific strategy per target. My current architecture follows:
[Application] -> contains -> [player] -> contains -> [renderer]
Currently, Renderer is an interface which needs to be replaced for different platforms:
Mobile -> uses MobileRenderer
Web -> uses WebRenderer
I have the freedom to use any bundlers - currently using Webpack for the app and Rollup for the lib - and what I could achieve follows:
My lib exports a Player interface and a createPlayer function which returns a PlayerInterface. Then, on the application side I have a Webpack alias that resolves the correct platform library based on a build input.
e.g:
import { createPlayer } from "my-lib";
const player = createPlayer()
Then we build the application with
npm run build --platform=web
to which webpack will transform the my-lib import into my-lib/platforms/web, which also contains an exported createPlayer function which uses the correct renderer.
My question is the, from the application's point of view, how can we make sure that we import the correct renderer per platform on build time while allowing tree-shaking (so only including the correct sources)? I find that using the build system to do that is quite obscure, as it doesn't leave a clear trace of what's going on.
Is there a better way of doing this?
Best,
You have a couple options. I'd recommend against having a compile-time switch, since that requires you to distribute multiple copies of your library (which isn't idiomatic). However, if you really wanted to do this, I'd suggest using webpack's ProvidePlugin or resolve.alias config field to dynamically link your code to the appropriate renderer at build time.
A more pragmatic approach, in my opinion, would be to have two entry points to your application, and allow the implementor to choose which entry point to use. This is similar to how react-dom switches between browser rendering and server rendering (i.e., react-dom versus react-dom/server):
// index.js
export * from './shared';
export {default as renderer} from './renderers/web';
// mobile/index.js
export * from '../shared';
export {default as renderer} from '../renderers/mobile';
Then, someone using your library could import {renderer, foo} from 'your-lib' or import {renderer, foo} from 'your-lib/mobile'.
Both of the above approaches work at build time.
While the latter approach normally forces you to choose which version of the library to use in your code, you can use webpack's resolve.alias configuration field to force imports to your-lib be redirected to your-lib/mobile.

Attempting to load a JavaScript sdk into an Angular2 application. Can't find all dependencies

I'm attempting to make use of this library: https://github.com/MagicTheGathering/mtg-sdk-javascript in an Angular2 application.
Unfortunately, I've been going in circles trying to load it into my application.
Firstly, on the TypeScript side if I import it using:
import { } from 'mtgsdk';
there are no types to load into the {}.
If I attempt to load it using something similar to:
import * as mtg from 'mtgsdk'
I'm unable to because it says that it's unable to find a module named mtgsdk.
I've installed the module using
npm install --save mtgsdk
Also, npm installs work fine for other modules.
The application compiles fine if I load it in using require via something similar to this:
var mtg = require('mtgsdk');
Taking that approach, I'm able to compile and launch but in the browser I get a number of errors about modules that it can't find. I figure they are prerequisites for the sdk that didn't get loaded so I start bringing them in via package.json.
For every one that I bring in, I then have to go to systemjs.config.js and add an entry pointing to the module's entry point and often have to specify a default extension using blocks like this:
pointer
'mtgsdk': 'npm:mtgsdk/lib/index.js',
'request-promise': 'npm:request-promise/lib/rp.js',
'ramda': 'npm:ramda/dist/ramda.js',
'emitter20': 'npm:emitter20/index.js',
'bluebird': 'npm:bluebird/js/browser/bluebird.js',
'request': 'npm:request/index.js'
default extension
'request-promise':
{
defaultExtension: 'js'
}
I'm not sure if that's the right approach though because the more dependencies I add, the more that I end up requiring. At one point I had literally gotten up to 50 extra dependencies added because every time I launched, the browser console would find more that were needed.
Is there any easier way to load all of these in?
Also, some of them (such as tough-cookie and request-promise-core) were very problematic to load and I couldn't get the browser console to stop complaining about them. Finally, some of them seemed very basic such as url, http, https. Those seem like they should already be present.
Using systemjs was utilized in the previous versions of Angular 2, However Angular 2 has evolved to Angular 4, with super new features like Angular CLI.
I recommend your use Angular CLI, with #angular/cli.
Importing Node modules
Since mtgsdk is a node-module, you can easily import it using
import * as mtg from 'mtgsdk'
However for your program to compile, you must install a type definition for it. or declare one for it in /typings.json or your app might not build.
Importing Client Scripts
For client scripts like firebase.js you won't need to add client scripts as entries in systemjs.config.js again.
Using #angular/cli, you would easily add them in the scripts[] array in your angular-cli.json for automatic compilation.
Then access them like this
declare const firebase: any;
Here is a quickstart tutorial to set up Angular with #angular/cli.

Categories

Resources