This file exists to explore different options for the API, naming, etc.
The language in general here needs some bikesheddding, as "Lazy" in the context of JS is often associated with "Lazy Loading", but this is not what is happening here. For now, lacking the right word, "lazy-eval" will sometimes be used.
The api can take a couple of forms, here are some suggestions:
Example using a hypothetical import attribute named "lazyInit" (subject to discussion, some alternatives below):
import {x} from "y" with { lazyInit: true }
import defaultName from "y" with { lazyInit: true }
import * as ns from "y" with { lazyInit: true }
Benefits:
- easy to read, clear intent
- clearly shows in the file that imports a lazy module, that there is lazy work being done
- per-use basis -- different files can import this as either lazy-eval or eager-eval
Drawbacks:
- requires the import attributes proposal
- per-use basis -- if you import a file lazily, you may want to import it lazily everywhere.
Alternative names:
... with { preferLazy: true}
... with { lazyHint: true}
Example using the keyword "lazy" as in lazy import
(subject to discussion):
lazy import {x} from "y";
lazy import defaultName from "y";
lazy import * as ns from "y";
Benefits:
- easy to read, clear intent
- clearly shows in the file that imports a lazy module, that there is lazy work being done
- per-use basis -- different files can import this as either lazy-eval or eager-eval
- doesn't require any support proposals
Drawbacks:
- per-use basis -- if you import a file lazily, you may want to import it lazily everywhere.
Example using the string "lazy-eval" (subject to discussion):
"use lazy-eval";
import foo from "./bar";
//etc
Benefits:
- easy to read, clear intent
- per-module basis -- always lazy!
- doesn't require any support proposals
Drawbacks:
- per-module basis, you may want to be certain that the file is loaded
- requires the use of a directive :(
- hides laziness from importing files. It isn't clear that the execution order will be changed from the importer.
Rather than specifying that something can be lazily initialized, we can instead assert that a module is pure:
import {x} from "y" assert { pure: true }
This would mean that you wouldn't need to explicitly make something lazy, it would be treated as lazifiable by the engine instead. This solves the side effect issue, but it makes other aspects more difficult. For example, can you only export function or can you export constants? What kind of constants? How do we detect clever ways to break this etc.
It also makes a distinction between what can and cannot be lazy. Throught the lazy getter, any module can be lazy. In this case, only a subset off modules can be lazy
Raised in issue #5 it is possible to instead have a prefetch
on dynamic import, and build this feature out of that.
// showing a "simplified" version.
function lazyAMethod(...args) {
const { aMethod } = import("./my-module", { with { prefetch: true }});
return aMethod(...args);
}
Object.defineProperty(globalThis, "someConst", {
get: function() {
delete globalThis.someConst;
const myModule = import("./my-module", { with { prefetch: true }});
Object.defineProperty(globalThis, "someConst", {
myModule.someConst,
writable: true,
configurable: true,
enumerable: true,
});
return myModule.someConst;
},
configurable: true,
enumerable: true,
});
function rarelyUsedA() {
// ...
lazyAMethod();
}
function alsoRarelyUsedA() {
// ...
use(someConst);
}
However the polyfill is quite heavy for this. Instead the ideal would be something that clearly communicates both cases.