ES6 Module loader to resolve symbolic references - javascript

We are trying to create an ES6 loader to use symbolic references to modules instead of well formed URIs or paths in import sentences. For instances, an import like this one:
import { foo } from 'com.bussines.platform.utils';
should be resolve the symbolic reference com.bussines.platform.utils to a real uri and proceed with the regular loading process to get foo.
To achieve this goal we are using the es-module-loader polyfill developed by #guybedford. As, we are in the browser context, we have create a loader delegating to BrowserESModuleLoader:
class CustomBrowserLoader extends RegisterLoader {
constructor(Loader) {
super();
this.loader = new BrowserESModuleLoader();
}
[RegisterLoader.resolve] (key) {
let uri = Resolve (key)
return this.loader[RegisterLoader.resolve] (uri); // (1)
}
[RegisterLoader.instantiate] (uri) {
return this.loader[RegisterLoader.instantiate] (uri); // (2)
}
}
The problem is that, surprisingly, calls at lines (1) and (2) doesn't find the required methods resolve and instantiate. We have tried other approaches based on inheritance but getting similar results.
¿Could anyone give some insights about how to solve the problem? Thanks in advance.

Loaders are separate builds, so I think you would want BrowserESModuleLoader.resolve and BrowserESModuleLoader.instantiate here.
It may be better though to directly fork the Browser ES Module Loader code itself, and adapt it to handle these types of specifiers directly instead.

Related

How to dynamically import moment.js in a react app (with typescript)

I'm trying to do some code splitting and moment.js is one of the targeted packages I want to isolate in a separate chunk. I'm doing it like this:
const myFn = async (currentNb) => {
const moment = (await import('moment')).default()
const dateUnix: number = moment(currentNb).valueOf()
[...]
}
But I get the following error: this expression is not callable. Type Moment has no call signatures . So I guess I should be doing something like moment.valueOf(currentNb) but I'm not really sure. Can someone put some light to this? Thanks in advance
So apparently my syntax was incorrect. I finally found how to solve this problem. I need to import it as follows:
const {default: moment} = await import('moment')
I found the answer in the webpack documentation:
The reason we need default is that since webpack 4, when importing a
CommonJS module, the import will no longer resolve to the value of
module.exports, it will instead create an artificial namespace object
for the CommonJS module.

How can I reload an ES6 module at runtime?

Prior to ES6 modules, it was (I'm told by other Stack answers) easy to force a JS script to be reloaded, by deleting its require cache:
delete require.cache[require.resolve('./mymodule.js')]
However, I can't find an equivalent for ES6 modules loaded via import.
That might be enough to make this question clear, but just in case, here's a simplified version of the code. What I have is a node server running something like:
-- look.mjs --
var look = function(user) { console.log(user + " looks arond.") }
export { look };
-- parser.mjs --
import { look } from './look.mjs';
function parse(user, str) {
if (str == "look") return look(user);
}
What I want is to be able to manually change the look.mjs file (e.g. to fix a misspelled word), trigger a function that causes look.mjs to be reimported during runtime, such that parse() returns the new value without having to restart the node server.
I tried changing to dynamic import, like this:
-- parser.mjs --
function parse(user, str) {
if (str == "look") {
import('./look.mjs').then(m => m.look(user))
}
}
This doesn't work either. (I mean, it does, but it doesn't reload look.mjs each time it's called, just on the first time) And I'd prefer to keep using static imports if possible.
Also, in case this is not clear, this is all server side. I'm not trying to pass a new module to the client, just get one node module to reload another node module.
I don't know what the reason behind doing this,
I think this is not safe to change the context of modules at runtime and cause unexpected behaviors and this is one of the reasons that Deno came to.
If you want to run some code evaluation at runtime you can use something like this using vm:
https://nodejs.org/dist/latest-v16.x/docs/api/vm.html
You could try using nodemon to dynamically refresh when you make code changes
https://www.npmjs.com/package/nodemon
I agree with #tarek-salem that it's better to use vm library. But there is another way to solve your problem.
There is no way to clear the dynamic import cache which you use in question (btw there is a way to clear the common import cache because require and common import has the same cache and the dynamic import has its own cache). But you can use require instead of dynamic import. To do it first create require in parser.mjs
import Module from "module";
const require = Module.createRequire(import.meta.url);
Then you have 2 options:
Easier: convert look.mjs into commonjs format (rename it look.cjs and use module.exports).
If want to make it possible to either import AND require look.mjs you should create the npm package with package.json
{
"main": "./look.cjs",
"type": "commonjs"
}
In this case in parser.mjs you will be able to use require('look') and in other files import('look') or import * as look from 'look'.

How to import as a new copy of object?

In my react application, I want to use a library (money.js) in 2 components which have different settings.
This library:
http://openexchangerates.github.io/money.js/
http://openexchangerates.github.io/money.js/money.js
I checked that javascript is using reference so the 2 components are actually referencing the same thing.
Does import create a new copy of imported library?
ES6 variable import by reference or copy
Is that possible without changing the source code (ideally), I can do something like the following
// a.js
import fx from 'money'
fx.rates={USD:1, EUR: 2.001}
// b.js
import fx from 'money'
fx.rates={USD:1, EUR: 2.002}
In my situation, I noticed that changing the rates in b.js, it also affect a.js.
No, there's no general one-size-fits-all way to clone a module or load separate copies of it. You may be able to do it, but it will be dependent on the specific way the module is coded.
It's unfortunate that the library you're using doesn't have the concept of an instance rather than making everything global to the library. You might consider adding that concept to the library and sending the original repo a pull request (if they decline it, you can always create a fork).
You can do it simply by adding query-param to module filepath:
// a.js
import fx from 'money?first'
fx.rates={USD:1, EUR: 2.001}
// b.js
import fx from 'money?second'
fx.rates={USD:1, EUR: 2.002}
you can clone that rates object in each file :-
var localRatesVariable = Object.assign({}, fx.rates);
Modules are evaluated once, that's one of their main properties. It's possible to re-evaluate a module in some environment that supports it (Node.js) but not in client-side application.
In this case the library is hard-coded to use fx.rates object. Even if it's copied, it will continue to use it.
A proper way is to modify library source code to support multiple instances.
An alternative is to create a wrapper that hacks the library to behave like expected. Considering that it's convert method that uses fx.rates and it's synchronous, it can be patched to swap rates property during a call:
import fx from 'money'
export default function fxFactory(rates) {
return Object.assign({}, fx, {
convert(...args) {
let convertResult;
const ratesOriginal = fx.rates;
try {
fx.rates = rates;
convertResult = fx.convert(...args);
} finally {
fx.rates = ratesOriginal;
}
return convertResult;
}
});
}
// a.js
import fxFactory from './fx'
const rates={USD:1, EUR: 2.001};
const fx1 = fxFactory(rates);
You can deep copy object:
let localRatesVariable = JSON.parse(JSON.stringify(fx.rates));

Leading Underscore transpiled wrong with es6 classes

I am experiencing a really weird behavior and can't even say which package to blame for it.
My setup: RequireJS project with the JSXTransformer and the jsx! plugin
I have an es6 class like this:
define([
'react'
], function(
React
) {
class MyComponent extends React.Component {
myMethod() {
otherObject.someMethod()._privateProp; // Yes, we need this accessing and have no influence on it
}
}
return MyComponent;
});
The transpiled output in the resulting bundle after running r.js is:
define('jsx!project/components/InputOutput',[
'react'
], function(
React
) {
var ____Class8=React.Component;for(var ____Class8____Key in ____Class8){if(____Class8.hasOwnProperty(____Class8____Key)){MyComponent[____Class8____Key]=____Class8[____Class8____Key];}}var ____SuperProtoOf____Class8=____Class8===null?null:____Class8.prototype;MyComponent.prototype=Object.create(____SuperProtoOf____Class8);MyComponent.prototype.constructor=MyComponent;MyComponent.__superConstructor__=____Class8;function MyComponent(){"use strict";if(____Class8!==null){____Class8.apply(this,arguments);}}
MyComponent.prototype.myMethod=function() {"use strict";
otherObject.someMethod().$MyComponent_privateProp;
};
return MyComponent;
});
Note how otherObject.someMethod().$MyComponent_privateProp; is written there. This obviously breaks because it is not a property on instances of MyComponent.
Add /** #preventMunge */ to the top of the file. See this GitHub issue:
Yes, sorry this is a non-standard fb-ism. For now you can work around this and toggle this feature off by putting /** #preventMunge */ at the top of your file -- but that's also a pretty big fb-ism. We should (a) turn this into a transform option (rather than a direct docblock directive) and (b) make it opt-in rather than opt-out (since it's non-standard).
For context: We munge all under-prefixed object properties on a per-module basis partly because our under-prefix convention applies to both objects and classes. Additionally, even if we wanted to lax the objects vs classes distinction, it's impossible to tell (in the general case) if a property is a reference to this since alias variables can occur (i.e. var self = this; self._stuff;).

How to properly use es6 classes in different files by importing them in Meteor?

I have recently discovered Meteor and I am struggling with using ES6 classes and imports in a new Meteor project. What I want to do is to have a complex structure of classes, which methods get called from Meteor events/methods/helpers. I've added Babel.js to the project by writing a command $ meteor add grigio:babel and it works properly.
Example of what I am trying to achieve:
in server/models/article.js:
class Article {
static all() {
//returns all articles from db
}
}
in server/methods/articles.js:
Meteor.methods({
allArticles: {
Article.all();
}
})
Having just that raises ReferenceError: Article is not defined in a methods file, which is adequate. So I have got three options: write all classes in one file, append all classes to a global object or use a good module system like Browserify. Obviously, third option is better.
But how do I use that? Babel converts export, import into Browserify by default and Meteor raises a require is not defined error on page refresh. After googling the problem I didn't find a clear solution on how to add Browserify to Meteor. Should I add a npm packages support to Meteor, add a npm package of browserify and add it manually to Meteor on every page where I import/export anything? Or should I use a completely different approach? How is this task usually handled in Meteor? Thank you!
I was reading about this earlier and found this issue on github that may help.
Essentially just assign the class to a variable that is exposed to both the client and server (lib/both/etc depends on your file structure). Like so:
Article = class Article {...}
Seems to be the best solution at the moment.
The way I do this is to collect objects together into various namespaces, for example:
// Global
Collections = {};
class Article {
static all() {
//returns all articles from db
}
}
_.extend(Collections, { Article });
Then to avoid having to use Collections.Article everywhere I can use the following in the file I need to access Article in:
// Make `Article` available
let { Article } = Collections;
I am using Meteor 1.4.1.1 and the error remains, when reproducing your approach. However, there are some new ways to use es6 classes now:
1. Export your class as a constant (e.g. for use as a singleton object):
class MyModuleInternalClassName {
//... class internals
}
export const PublicClassName = new MyModuleInternalClassName();
You can import this one via
import {PublicClassName} from 'path/to/PublicClassFileName.js';
2. Export your class directly as the module's default
export default class PublicClassName {
//... class internals
}
and then import it (as with the above one) as the following
import {PublicClassName} from from 'path/to/PublicClassFileName.js';
let myInstance = new PublicClassName();
+++++++++++++++++++++++++++++++++
Regarding the question of OP and the error, you can try something like this:
Article.js
class InternalArticle {
constructor(){
//setup class
}
all() {
//returns all articles from db
}
register(article){
//add article to db
}
}
export const Article = new InternalArticle();
Import and use the Singleton
import {Article} from 'path/to/Article.js';
//either register some article
Article.register(someArticle);
//or get all your articles
const allArticles = Article.all();

Categories