samthor/kuto

Dynamic imports support?

kurtextrem opened this issue · 5 comments

Suppose the following code:

a.mjs

const A = "bc"
console.log(A)

import("./b.mjs").then(() => {
    console.log("b.mjs loaded")
})

b.mjs

const B = "de"
console.log(B)

The output is then:
a.js

const A = "bc";console.log(A);$1._1import * as $1 from "./a.kt-01tEKKx.js";

a.kt-xxxx.js

export var _1=import("./b.mjs").then(() => {
    console.log("b.mjs loaded")
});

So it seems like the generated output is not valid JS, so I assume Kuto does not support dynamic imports?

If you're wondering about my use-case, I have a main bundle where I import routes via import(...). I hoped that either Kuto would do the chunking / splitting automatically, or I could could feed e.g. esbuild splitting=true output, run Kuto over it and Kuto would do this:

main bundle 
|-------------> static (kuto)
|-------------> route1 (dynamic import split by esbuild/kuto)
                              |-------> static_route1 (kuto)
                              |-------> static_route1_route2 (shared static, kuto)
|-------------> route2 (dynamic import split by esbuild/kuto)
                              |-------> static_route1_route2 (shared static, kuto)

extract into dynamic chunks that have their static parts also separated

Ahh cool, thank you for the quick reply!!

Finally, note that Kuto at least now doesn't plan to "introspect" any
imports. It's not a bundler. So it should work but it won't peer into
"b.js".

What would be the best approach to "Kuto-ify" the routes? Run kuto on each file separately?
(If yes, that would probably mean it can't generate 'common' static - would certainly be a nice feature for sites with many code splits)

It's late here and I will look more tomorrow ;)

As for multiple imports... I guess there's two things I'm thinking about:

  1. Is Kuto intended to be a bundler - i.e., is it advantageous for this approach to know the original source tree - I haven't quite come to a conclusion on this yet. I know that isn't your question but it maybe feeds into the second point.

  2. Right now, I take a very simplistic approach to ESM and Kuto works well because it's a single file.

In your example, the static_route1_route2 file may need to reference parts of route1 or route2 to get its job done, say, a top-level const available in route1. If the static chunk is imported by route2, then it imports route1 as a side effect, which defeats the point of lazy-loading.

I can imagine a reduced version of Kuto which prevents "referencing back" to any main files, so basically supporting a subset of functions. I need to think if that's useful enough. I could honestly build it and put it under a flag.

(Another idea might be dependency injection style, where static_route1_route2 is created and then only populated with the things it needs from route1 or route2 when they start, perhaps by lazy dynamic import, but now we're going down the rabbit hole:

// route1.js
let x = 123;

import * as $1 from './static_route1_route2.js';
await $1.readyRoute1({ x });

export { _1 as addOneToGlobal };

// static_route1_route2.js
let globalRoute1 = undefined;
export const readyRoute1(vars) { globalRoute1 = vars };

export const _1 = () => globalRoute1.x + 1;

... this may not be a good idea)

I've fixed the first two bugs (plus fixed some support for top-level await) in a new deployed version 0.3.6.

Kuto should now 'succeed' on your code, but still only on a single file—it's not looking inside imports to do anything sensible. I would love it to but I think it involves me going to a whiteboard for a few days and thinking about ESM some more 🤔

I can confirm the fix, thank you 🙌🏻

Yeah, I think making dynamic imports work is hard, because you cannot 'just' create one single bundle per dynamically imported chunk. An example where "create one bundle before running kuto" would break is a shared React context imported by route1 & route2; if the context gets inlined into both bundles, it would lead to a changed runtime behavior.
To fix that, Kuto would probably need to become a bundler (because it would need 'awareness' of a module graph or so).

However, we could also say running kuto one a single file (which includes chunks that are dynamically imported) would kind of allow to kuto-ify it (just not with the maximum efficiency, because as mentioned it's not possible to create one big bundle before, so if e.g. esbuild has split out some things already, it will stay as-is)