Thanks for the code example, basically the MockDriver can be addressed if I give the driverName the value āMockDriverā that is 100% true. However you are the author of that module and can decide in the end what is and what is not being loaded into the current context. I guess if there is a security concern youād apply other checks too.
For a dangerous driver factory you can do this (and it is perfectly valid and way more dangerous than the reflect):
This exposes way more security concerns. (Because of this for instance cloudflare heavily limits the types paths import can use)
my approach would do this for cases where you donāt know the class but you simply have a contract with it! For instance a module or another script loaded the utilities but the factory doesnāt need to know the exact folder where itās at, it just knows the contract it has and tries to build the instance for you. This works for instance if you have ini files that are loaded by the factory and then it itself builds the instances for you - but it does not manage the actual load thereof.
So say you have a DB connector and an ini file that tells it the host, the port, the user, password, database connection charset etc⦠The factory knows how to set all those up for you using various method calls. Without reflect you need to either rely that the factory knows where to load its drivers from / which almost certainly means it canāt load your custom driver without creating a gaping security hole ( using custom drivers for db may sound stupid till you need a http proxy or other unorthodox stuff for you infrastructure). Reflect would allow it to view the entire global scope + local scope, so if your driver is loaded in global scope which is what it assumes it would work seamlessly and also not take away control over what is being loaded so you can make sure it uses the correct implementation.
I looked through my own code to see if I could find any uses of eval. I found one. (By āevalā, Iām talking about anything that evaluates JavaScript - I technically used new Functino()).
Iāve got a webpage that hosts many helper functions you can copy and paste into your own codebase. So, thereās an overview page showing a list of all helper functions available, you click on one and it opens a side-panel where you can view the code sample to copy-paste it, and thereās a description underneath explaining how to use the code sample.
Behind the scenes, I organized it by placing each code sample into its own folder. There would be a code.js file, holding the actual copy-pastable function, a description.md file, holding the details that appear under the code sample, and sometimes a test.js file making sure the code sample is behaving as intended - especially if I ever make modifications to it. A custom build script would run over the folders and put all of this stuff together into a JSON format that the webpage can fetch and display.
Testing was the tricky bit. I never use export in the code.js files, because I didnāt wish to have the word āexportā be part of the copy-pastable code snippet the end-user saw. So, what I instead did was eval-ed the contents of the code.js file - then I could get a reference to the function contained within and test it.
Are there other ways to acomplish it without using eval? Sure, I can think of at least one other path I could have taken. But I also donāt believe there was anything inherrently wrong with the way I used eval (well, new Function()) in this context. I have a piece of JavaScript that I needed to evaluate, so I did.
My biggest issue with with providing automatic access to the local scope (something that Javaās Class.forName() doesnāt do). Your last example sounds like it doesnāt really need access to the local scope - youāre talking about adding a custom driver to the global scope so that the package can find it - that sort of thing is trivially possible today - the package can look up drivers in its allowed set of drivers then fall back to looking it up in the global scope, without ever granting access to its local scope and worrying about encapsulation leakage.
import { MySqlDriver } from 'mysql-driver';
import { D3Driver } from 'd3-driver';
const drivers = { MySqlDriver, D3Driver };
export function sendSqlCommand(driverName, command) {
const Driver = drivers[driverName] ?? globalThis[driverName];
const myDriver = new Driver();
...
}
Interresting usecase, There are multiple ways to deal with it: use a service worker intercept the call and let the service worker wrap the user script in a harness script (that create the unit test type scenario for you) and returns it inside an iframe sandbox and marshal all the calls. You can do the same with an edge worker or if none of these are available with a server script. Alternatively you could use a js parser to parse the user structure into an executable structure that is insulated.
There is nothing inherently wrong with eval, it just is a bandaid that is no longer needed, there are solutions for anything you can ever want. I for one have stopped using eval and new Function at least a decade ago, unless it was for hacks where I just needed something to run once and I couldnāt be arsed to do it right, but thatās the thing use it once and throw away, not part of the permanent codebase.
What I meant was to have a class say PGGSQL that I loaded in global scope. Then run a module that is a driver factory and give it a name, it loads the ini file finds that the class name is PGSQL it checks that the class is available and instatiates it. Yes boilerplate is usually trivial stuff. mostly bookkeeping and linkages paths etc⦠but why needing it if you donāt
Also Class.forName(āā) in java gets you any public class within the classloader isolation silo. You can hide classes inside other classes and make them invisible to others but aside from the fact that this is frowned upon it makes the entire code base a mess to debug. In java if you really need isolation you use a classloader + annotations for policy, this ensures that across classloaders classes can talk but are marshalled. Also Class.forName has access to internal medium so it can see any local nested class along with the public ones. Also this all relies on the fact that you can not willy nilly load classes, you need a classloader to do it for you, so the classes are siloed to their respective classloaders.
Remember the context - I'm evaluating code I wrote, not untrusted code - there's no real reason to insulate the code. So all of these solutions are a much more complicated way to accomplish the exact same thing. If I'm worried about insulating my own code, then by extension, I shouldn't use es import syntax either, as that effectively āevaluatesā other modules I wrote in the same realm, I should instead run every single module in its own isolated realm, just in case I wrote something bad in one of my modules. But I'm ok letting my code trust itself, so I'm ok evaluating my own code - simplifies a lot that way.
I'm ok if you disagree and feel it should never be used. Either way, as mentioned, it would be web incompatible to remove it, so it won't be removed. As jschoi mentioned, other web specifications are a bit looser with the ādon't break the web" philosophy, and it's why you were able to list a handful of things outside the JavaScript specification that were removed. But the JavaScript specification itself has historically been very strict - it's why things like āwithā syntax still exist, and was never removed. And I'm personally grateful for it - I like being able to put up webpages, and then not have to worry about constantly updating them due to breaking changes in JavaScript. I like being able to use the wayback machine to dig up old pages and blog posts and be able to still view them. It pains me that other specifications aren't as strict as JavaScript - I'm going to miss not having those old flash games around :). And if this web compatibility stuff comes at the cost of never removing old functions from the JavaScript language, so be it.
You may have to provide some examples code to help me see this better.
Oh, ok, I thought the users could alter the code before you ran it, if it is your code itās even easier just have the code samples duplicated in a utility class and call them from there and not have to deal with anything.
I understand the allure of forever backwards compatibility, I do, but honestly for something new to grow the old must die and make space. If we keep on bolting stuff on at one point it will buckle under its own weight and some dude at some super powerful and influential corporation controlling a browser controlling 80% of the internet traffic will just say enough and soon enough weāll have againā¦We donāt need to change the existing codebase just add a new one and watch the old slowly die off, just as much of the ie stuff died out. I had built entire htma applications around active-x learned all the apis which was a total nightmare because documentation was sparse and usually outdated, none of them run today and itās a good thing! This entire thing is pure political, if one day google decides to write a new html engine from ground up and have a new mimetype or whatever hook it up with new and clean javascript with good security silouing etc. in a matter of a decade all those sites that run current js will be obsolete.
Macromedia was building itself a nice little empire, then apple came with html5 and adobe bought macromedia at a discount⦠At one point everybody and their mother ran flash!
If you want an analogy OpenGL is a glaring one⦠open standard - never ever obsoleted anything - got so complex and bloated nobody likes it nowadays. Perl, another never obsolete a thing - got obsoleted itself same as OpenGL, Vulkan basically replaced it - people felt a reboot was necessary.
As a counter example is php, 75% of the internet still to this day runs on PHP and yet they obsoleted a lot of crap over the years, they fixed their shitty oop model etc. Same with java they improved the language a LOT! So many contenders tried to steal the internet from under PHP⦠including javascript pushed by google for at least a decade⦠and still it absolutely dominates, a language that originally was a templating language⦠And I feel the only reason php si dying now is because of edge workers where php has no business being.
Also if sites need to be maintained every few years itās a good thing, they evolve or die and make space for others to take their place!
As to the code:
The site or environment loads the driver directly and makes it available in the global scope then the factory will be able to use it much like a known driver because of the contract, since java has no interfaces it would be some type of agreed upon api. Doing this makes the factory not able to load random code but helps you build the instances correctly.
Your belief that back compat shouldn't be preserved is irrelevant. It will eternally be preserved, since every player in the space is highly invested in preserving it.
It's not worth anyone's time to discuss removal of eval - it's never going to happen.
It is true that other web standards, like HTML, have ābroken the webā by deprecating then removing certain selected features. Like I said above, āSome web standards do take into account whether a feature is used by practically nobody and then cautiously remove it. JavaScript is not one of them.ā JavaScript has stricter backwards compatibility requirements than the other web standards.
I actually kind of wish that JavaScript would be more open to making a backwards-incompatible version, but we all know that this would be a trade-off. And the trade-off that the JavaScript standard has made, for the foreseeable future, is for increasing reliability through perpetual backwards compatibility for existing JavaScript programs.
This isnāt a ādeath by committeeā phenomenon. It is a āboring technology should stay boringā and āreliable technology should stay reliableā phenomenon. (I myself may wish our goals and constraints were a little different, but I can at least understand why they are the way they are.)
Just gonna leave it here: eval is not evil. There are perfectly safe and valid ways to use it. I understand not everyone shares that opinion, but dynamically loading and evaluating code shouldn't be a power restricted to embedders.
Common use cases are plugin/module systems for an application. It's unfortunate that on the web today you effectively have to embed a wasm JS engine to do this.
Yes, no breaking backwards compatibility, but none of the sites built to run on ie 5 or older still run today⦠itās all politics - If tomorrow google decides to ship a new and improved js engine they will and the committee will just receive notification there-off to adopt quickly the new standard which will evidently be quickly rubberstamped IE how html5 and all its new apis was passedā¦
Want me to give you an example? Simplest one I can think off that broke basically all IE sites: attachEvent - where is it? And that is just javascript era sites, wonder if any of you still remember VBScript?
So, no I donāt buy that one bit! When a dominant company goes under suddenly it turns out we can modify backwards compatibility & breaking stuff⦠funny that!
Donāt get me wrong I am happy IE died and I hope it rolls in the pits of eternal past, but it goes to show that what matters here is just whoās top dog! If tomorrow a new company becomes the maintainer of the dominant browser youāll see how quickly things start to change again.
Evil it is not, it never was, heck it saved many a desperate developerās bacon⦠But what can be done is to replace it with a sensible api something like
let engine = new ECEngine({sandboxed:true}); // options for tokenisation and maybe even templating later on - maybe sandboxed to be default mode.
engine.load("code here"); // equivalent to an inline script tag
engine.loadFromFile("file name here");// load a file similar to a script src
engine.getParsedTokens();// return the token structure for DSLs implementations
engine.run(); // run the code
All calls could return a promise to make handling errors and anything else. Can you imagine just how much easier it would be to implement DSLs
That's assuming eval isn't sensible in the first place. You may want to evaluate code in a new agent, a new realm, or the current realm but possibly in a compartment. It's for the application to decide based on their use case. Something like you're suggesting with your "engine" is effectively equivalent to a new realm with a strict interface. It would be equivalent IMO to doing an eval (or importing a module source text) in a ShadowRealm. Also I would consider getting an AST or equivalent from source to be an orthogonal feature.
Anyway, I see no reason to try to deprecate or remove eval.
As I said you could control the level of siloing, it can be completely shut off or it could run in the same context as the rest of the code, but the developer decides that! Getting the tokens structure could help a lot by removing the need of js implemented parsers for instance, not to mention the huge speed uplift. Also getting the structure is not necessary but it is a low hanging fruit, the engine already builds that structure anwyay
Hi.
Sorry for inconvenience. Can you please review these code parts for better discussion focus ?
Firstly as there is no Nominal Typing we may rely on avoiding re-naming and deletion of Function
Name, and moreover it is not obligatory String:
So there is no way to delete or update .name field after this point.
Then we may share a Map, having all the pointers as we need, it is just IoC container.
// 2. making IoC store
const ConstructorsIoC = new Map();
ConstructorsIoC.set(MyConstructorSymbol, MyConstructor);
// this logic will help when you have no pointer to the constructor
So let test it then, as full code piece:
// 1. having strict names for Constructors
const MyConstructorSymbol = Symbol('My Constructor');
const MyConstructor = function () {
Reflect.defineProperty(this, 'tesingTheName', {
get () {
return this.constructor.name;
}
});
};
Reflect.defineProperty(MyConstructor, 'name', {
get() {
return MyConstructorSymbol;
}
});
Object.freeze(MyConstructor.prototype.constructor);
// 2. making IoC store
const ConstructorsIoC = new Map();
ConstructorsIoC.set(MyConstructorSymbol, MyConstructor);
// 3. getting the constructor instance from IoC
const myInstance = new (ConstructorsIoC.get(MyConstructorSymbol));
console.log(myInstance.tesingTheName); // Symbol(My Constructor)
Therefore I indeed think the thing is not about using eval but bringing more code quality.