5

I have a website made in ReactJS. The website calls functions of a library analyzejs generated by another programming language. I can only call functions of this library; I cannot modify much the content of this library.

So far, I have been using the first version of the library: analyzejs-v1.js. To achieve this, in frontend/public/index.html of the website, I have

<head>
  <script src="/lib/analyzejs-v1.js"></script>
  <!-- <script src="/lib/analyzejs-v2.js"></script> -->
</head>
<body>
  <div id="root"></div>
</body>

I also have a type declaration file frontend/src/defines/analyzejs-v1.d.ts as follows:

declare function f1(): string;
declare function f2(): string;
...

As a result, in the code of the website, I could call directly f1() to use analyzejs-v1.js.

Now, I would like to add another version of the library analyzejs-v2.js. In some part of the website, I want to call f1() of analyzejs-v1.js; in some part of the website, I want to call f1() of analyzejs-v2.js. So I guess I need to add namespaces such as v1 and v2 to these different versions to avoid conflict. Then, I will be able to call v1.f1() and v2.f2().

I tried to modify frontend/src/defines/analyzejs-v1.d.ts as follows:

declare namespace v1 {
    function f1(): string;
    function f2(): string;
    ...
}

And in the code of the website, I tried to use v1.f1().

The compilation did not raise any error. However, running the website and using features calling v1.f1() returned me an error on v1.f1(): Unhandled Rejection (ReferenceError): v1 is not defined.

Does anyone know how to add a namespace to such a library?

SoftTimur
  • 5,630
  • 38
  • 140
  • 292

4 Answers4

0

If you import these files in your code like so:

import analyzejs-v1 from '/lib/analyzejs-v1.js'
import analyzejs-v2 from '/lib/analyzejs-v2.js'

in your code you can call them under different objects:

analyzejs-v1.f();
analyzejs-v2.f();
K41F4r
  • 1,443
  • 1
  • 16
  • 36
  • I could not change much about `analyzejs-v1.js` and `analyzejs-v2.js`. In ReactJS, is it possible to put them under objects? – SoftTimur Oct 20 '21 at 17:35
  • @SoftTimur updated answer, do you have to import these as script tag? – K41F4r Oct 20 '21 at 17:40
  • I just tried import, the compilation raised an error `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`, probably because my `/lib/analyzejs-v1.js` is too big (6Mo). – SoftTimur Oct 20 '21 at 17:55
  • https://stackoverflow.com/questions/34607252/es6-import-module-from-url – Roberto Zvjerković Oct 20 '21 at 18:05
0

The best way would be to group the functions inside your javascript files into classes or objects

// test.js
const testNamespace = {
    print = () => console.log('print was called from test.js')
}
// test1.js
const anotherNamespace = {
    print = () => console.log('print was called from test1.js')
}

now you can call these functions using


anotherNamespace.print();
testNamespace.print();
AnanthDev
  • 1,605
  • 1
  • 4
  • 14
  • I could not change much about `analyzejs-v1.js` and `analyzejs-v2.js`; I need to modify on the side of ReactJS. – SoftTimur Oct 20 '21 at 17:50
0

Problem - you are only using typescript type declarations, which are deleted on compilation, completely not doing anything at runtime. JavaScript actually doesn't support namespaces, only global scope, function scope and block scope.

Solution #1 - using es6 import syntax, which creates a function scope around the imported script (referred to as a module). And even better, React supports dynamic imports, which gives you both a module "namespace", and automatic code splitting, which could help with your heap memory issues:

// Page1.tsx
import("/lib/analyzejs-v1.js").then(module => {
  module.f1();
  module.f2();
});
// Page2.tsx
import("/lib/analyzejs-v2.js").then(module => {
  module.f1();
  module.f2();
});

The dynamic import splits the JS bundle so that the dynamically required JS parts are in separated files, and run only when import() is called. This could help with really large files and memory issues. import() returns a promise, which gets the module as an argument when resolved.

Solution #2 - use the import syntax import * as moduleName from "module/url". This import method contains the functions in the module to an object with the name of your choosing, but everything will be in the main bundle as apposed to dynamic imports:

import * as moduleV1 from "/lib/analyzejs-v1.js";
import * as moduleV2 from "/lib/analyzejs-v2.js";
      
moduleV1.f1();
moduleV1.f2();

moduleV2.f1();
moduleV2.f2();
deckele
  • 4,623
  • 1
  • 19
  • 25
  • Thank you. All my functions that call `f1()` and `f2()` are non-asynchronous. If I want to use `import` as you suggested, does it mean that I need to change all these functions to asynchronous? – SoftTimur Nov 13 '21 at 22:09
  • @SoftTimur In this case, I'm using `.then` on the dynamic import promise, so no need to change the function to async. But of course, it really depends on want you want to do. – deckele Nov 13 '21 at 22:25
  • You are already using `.then` in `import("/lib/analyzejs-v1.js").then(module => { ... })`, no? I don't see how to write it in other ways. – SoftTimur Nov 13 '21 at 22:30
  • Additionally, `import` gives me a warning `File '....../frontend/public/lib/analyzejs-v1.js' is not a module.` – SoftTimur Nov 13 '21 at 22:31
  • @SoftTimur so perhaps your package does not support the syntax. Could you try just using: `const moduleV1 = require("/lib/analyzejs-v1.js");` `const moduleV2 = require("/lib/analyzejs-v2.js");` then use moduleV1 and moduleV2 as your namespaces. – deckele Nov 13 '21 at 22:34
  • `const moduleV1 = require("/lib/analyzejs-v1.js");` or `import * as moduleV2 from "/lib/analyzejs-v2.js";` return `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`. https://pastebin.com/raw/5GLX5k2v – SoftTimur Nov 13 '21 at 22:45
  • @SoftTimur OK, another complementary approach would be to use import/require, but in a worker thread, in an attempt to save some memory: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers – deckele Nov 13 '21 at 22:56
0

So you want to wrap different versions of a vanilla javascript library into a bundled script library and selectively call either in a browser?

This solution uses npm, browserify, require and module.exports.

The strategy is to add a module.exports to the vanilla libraries and then bundle them together as globals v1 and v2.

So setup a folder for building the bundled libraries.

$ mkdir bundle-analyzers
$ cd bundle-analyzers
$ npm init

package name: (bundle-analyzers)
version: (1.0.0)
description: bundle versions of analyzejs with browserify
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)

Create a lib folder and copy the analyzejs files into it

+--bundle-analyzer
|   +-- lib
|   |   analyzejs-v1.js
|   |   analyzejs-v2.js
*   

For simplicity, let's assume both analyzejs versions have a common method named version(). Let's expose both by adding the following to the end of each analyzejs-v*.js file:

module.exports = { 
  version
}

Create a file analyzejs-bundler.js which will expose both library's exports globally:

var v1 = require("./lib/analyzejs-v1.js");
var v2 = require("./lib/analyzejs-v2.js"); 
window["v1"] = v1;
window["v2"] = v2;

Install browserify in the root folder

$ npm i browserify

Add an index.html to the project folder which will demo the bundle:

<html>
<head>
  <script src="./bundle/analyzejs-bundle.js"></script>
</head>
<body>
  <pre id="versions"></pre>
  <script>
    window.addEventListener('load', () => {
      document.getElementById('versions').innerText = [v1.version(), v2.version()].join('\n');
    });
  </script>
</body>
</html>

Now add a script to package.json to create the bundle:

"scripts": {
  "bundler": "browserify ./analyzejs-bundler.js -o ./bundle/analyzejs-bundle.js"
}

Create the bundle

$ npm run bundle

OK so the folder structure should be

+--bundle-analyzer
|   +-- bundle
|   |   analyzejs-bundle.js
|   +-- lib
|   |   analyzejs-v1.js
|   |   analyzejs-v2.js
|   +-- node_modules
|   |   { node stuff }
|   analyzejs-bundler.js
|   index.html
|   package-lock.json
|   package.json
*

Open up index.html from the file system and verify.

Of course if there a zillion exports from the analyzejs library something more sophisticated will be required.

Note it is also possible to create a monolith for versions of analyzejs which may be more suitable:

var v1 = require("./lib/analyzejs-v1.js");
var v2 = require("./lib/analyzejs-v2.js"); 
window["analyzejs"] = { v1, v2 }

YMMV. The ambient definitions for Typescript depend on the global variables defined and are simply a tedious sideshow to the solution IMO.

RamblinRose
  • 4,883
  • 2
  • 21
  • 33