Dynamic import hook - #1790
Conversation
|
It is worth also mentioning that this work is effectively flagged, as it requires the |
|
Hi @guybedford. Just note I am a little short on time working on rollup at the moment so reviewing this will take a few days, nevertheless thanks for all the effort you are putting in at the moment, I'll be in touch shortly. |
|
@lukastaegert thanks for the update! No worries, when you are ready, and thanks for taking the time. |
|
How does inlining behave exactly? Assume the following: If inlining hoists importing of internal module Or does inlining work if and only if And in this scenario, I'm sketching out some drawbacks/misconceptions rollup users may have using async imports so a relevant documentation can be updated accordingly. |
|
@anilanar you've got the interpretations exactly right here in the second example - inlining only happens when the module is already included statically in the build. As for the use cases around dynamic import, some different thinking is needed to make these work out. I hope to share my project for handling deduplication along these lines next week! |
|
Note one other optimization that could be added would be to use static analysis to trace which exports are used by the dynamic imported form. For example: import {b} from 'x';
import('x').then(x => {
x.a();
});Could know that we only use Happy to move in these sort of directions in due course with further PRs as well. |
lukastaegert
left a comment
There was a problem hiding this comment.
Ok, so I had a long thought. These are my results:
- Usually when people request an unsupported feature, we point them to using a transpiler. Obviously, this is not possible for dynamic imports.
- Once dynamic imports become an official feature, it would be nice to already have solid and tested support.
To that end you got me convinced that we should have this feature. I like how you implemented the hook though I would like to extract the plugin code.
As for the implicit feature flag:
I would rather have a real rollup feature flag instead (that also takes care of any necessary acorn options).
- This would abstract the plugin away from the user completely and allow us greater refactoring freedom
- If the feature flag is not set, no plugin should be added to acorn; if it is set, no other options should be necessary
- I would suggest to call it
experimentalDynamicImports. Why "experimental"? Because I want it to be clear to the user that breaking changes to the feature are expected until we are sure this is "stable". That should give us (especially you!) more freedom in exploring different avenues (like automatic code splitting...) without (hopefully) angering the users too much. - Once we think this is stable AND acorn has official support, I would suggest to remove this feature flag again such that dynamic imports always work the way we decided they should work.
- As plugins have the ability to modify options, dedicated plugins can change the feature flag for you if they need to.
Tell me what you think. I would really love to have you on board for this as you seem to have a lot of great ideas and the experience to support them.
Otherwise with the changes outlined above, I would merge this and create a new release.
| }; | ||
| }); | ||
| }; | ||
|
|
There was a problem hiding this comment.
If we could extract the plugin code to a separate file, that would be great. Especially since I expect this code to be removed as soon as acorn rolls out official support. This would also align nicely with one of my current goals to clean up Bundle.js and Module.js as much as possible 😉
There was a problem hiding this comment.
Sure I've done this.
There was a problem hiding this comment.
Btw, I made a PR to check how alive that plugin is: kesne/acorn-dynamic-import#11. In case it's not maintained I would also be willing to fork it.
| description: 'Dynamic import expression replacement', | ||
| options: { | ||
| acorn: { | ||
| plugins: { dynamicImport: true } |
There was a problem hiding this comment.
I tried setting these flags to false in all tests but that did not have any effect. Are they actually required?
There was a problem hiding this comment.
They are definitely required and will cause an error in acorn otherwise. I've definitely seen the error in my tests. let me know if you are still getting this though.
|
Thanks @lukastaegert for the feedback here, I'm really glad to hear that API sounds sensible to you. An experimental flag would be good for this - I've added this as |
lukastaegert
left a comment
There was a problem hiding this comment.
Thanks a lot, from my side this is good to go into the next release!
| @@ -1,4 +1,5 @@ | |||
| import { parse, plugins as acornPlugins, tokTypes as tt } from 'acorn'; | |||
| import * as acorn from 'acorn'; | |||
There was a problem hiding this comment.
Nice touch, I think using the namespace here certainly improves readability
| return function setModuleDynamicImportsReturnBinding ( _moduleDynamicImportsReturnBinding ) { | ||
| moduleDynamicImportsReturnBinding = _moduleDynamicImportsReturnBinding; | ||
| }; | ||
| } No newline at end of file |
There was a problem hiding this comment.
I'd still prefer this to use the existing plugin, but I can understand that you don't want to have a dependency on another different acorn version. I hope kesne/acorn-dynamic-import#11 gets merged soon, otherwise I'll fork the repo.
There was a problem hiding this comment.
I agree that having a publicly supported plugin is probably preferable as long as it is properly supported. However since now everything is behind a feature flag and the plugin logic is not exposed to the user, changing this later should not be a problem.
There was a problem hiding this comment.
Sure, this is fine as a start. Since I'm using acorn-dynamic-import in acorn-stage3, I want to make sure one way or the other that there is a maintained version.
There was a problem hiding this comment.
Let me know if you end up forking it and I can change my pull request at kesne/acorn-dynamic-import#10 to point to the new fork.
There was a problem hiding this comment.
kesne/acorn-dynamic-import#11 is merged and there should be a release soon (after kesne/acorn-dynamic-import#10 landed, too). Should I prepare a PR?
There was a problem hiding this comment.
@adrianheine if you're interested that would be amazing. Would likely have to come after #1857.
|
I was just going to say actually I might have an alternative approach here.... |
|
@lukastaegert can you DM me on twitter? |
|
+1 👏 |
|
Actually I kinda messed up this merge anyway because I overlooked some issues with another PR and think I might start assembling the release from scratch tomorrow. So no harm done, I can leave out this PR if you have got a better idea. |
|
It’s kinda late today but we can talk tomorrow |
|
Sure, happy to merge this as-is. I realise now what I was thinking is along the lines of a default dynamicImport hook, which fits into how all the hooks work, so there are no conflicts of direction. But it would be good to chat about some of this some more at some point if you have a moment, as I've found it's easier to build this sort of tooling when being able to hook into Rollup through the Bundle API over the rollup.rollup API. |
This implements a dynamic import hook for Rollup, roughly along the lines as discussed in #1779, although with a slightly different API.
The concept is that it should be possible to construct plugins for Rollup that can handle use cases such as:
import('lodash')being resolved toimport('https://cdn.com/lodash')import('y').then(y => console.log(y))whereyis already statically included becomesPromise.resolve(yNamespace).then(y => console.log(y))import('./local' + viewName)being updated toimport('https://site.com/templates/' + viewName).The API suggested to handle all of these cases is a plugin hook that looks like:
The cases then work out as the following:
import('x')), we pass the string value directly as the value ofspecifier, and the parent module name asparentId. The returned string is then used as the new specifier to write into the dynamic import.resolveDynamicImportfunction corresponds to a module name that is already in the bundle we are busy building, then we inlinePromise.resolve(namespace)in its place.resolveDynamicImportfunction with thespecifiervalue as the original AST node. The returned value is then taken not to be a resolved module specifier literal, but rather the replacement source to use within the import expression.Alternatively just look at the tests and I'm sure it will make sense.
With this, I can get dynamic chunking workflows around Rollup. Very excited by the possibilities I must admit.
Feedback very welcome - happy to climb hills to get this in.