Assign namespaces to src library files

Viewed 181

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?

4 Answers

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();

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();

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();

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.

Related