Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Dynamic import w/ template literals w/ variables #56

Closed
vsn4ik opened this issue Apr 7, 2020 · 16 comments
Closed

[Feature] Dynamic import w/ template literals w/ variables #56

vsn4ik opened this issue Apr 7, 2020 · 16 comments

Comments

@vsn4ik
Copy link

vsn4ik commented Apr 7, 2020

Dynamic import w/ template literals w/ variables problem. Used esbuild@0.0.15.

// x.js
export const a = 1;

// test.js
const name = 'x';
(async () => {
    await import(`./${name}.js`);
})();

Run: esbuild --bundle test.js --outfile=build.js

Expected output:

Wrote to build.js

Actual output:

test.js:3:17: error: The argument to import() must be a string literal
@evanw
Copy link
Owner

evanw commented Apr 7, 2020

Bundling with esbuild is intended to traverse your whole dependency tree and include all of your dependencies in the bundle. However, it can only do this if the dependency tree is statically analyzable. This is why esbuild requires string literals. Dynamically-computed import paths are not statically analyzable.

Can you say more about your use case? What are you using this to try to do?

@CyberAP
Copy link

CyberAP commented May 5, 2020

Is it possible to extract all the dynamic parts of a template literal expression and handle them like a * wildcard?

@traverse
Copy link

traverse commented May 8, 2020

An example use case would be dynamically loading translation files which is what I use it for at work. e.g. import(`/translations/${language}`)

@kilianc
Copy link

kilianc commented May 13, 2020

@traverse do you have a bundle per language or do you import the language at runtime?

@traverse
Copy link

@kilianc the translations files are simple JSON files per language but since we're using webpack it creates bundles and a bundle map for us so that they can be loaded dynamically at runtime. You can find some more info here https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import. Like it states in the documentation though the path can't be fully dynamic.

@evanw evanw changed the title [Bug] Dynamic import w/ template literals w/ variables [Feature] Dynamic import w/ template literals w/ variables Jun 12, 2020
@evanw
Copy link
Owner

evanw commented Jun 12, 2020

Since it's relevant to this thread: as of version 0.5.3, dynamic imports with template literals with variables are now a warning, not an error. The dynamic import is passed through to the output code unchanged.

At the moment I don't have any plans to emulate a virtual file system like Webpack does. That feature in Webpack seems too complicated and special-cased to be built into esbuild itself in my opinion, especially with all of the Webpack-specific comment directives.

One option is to wait for esbuild to have plugin support and then write a plugin for this (see #111). Another option is to just rewrite the import expression in your source code if it is intended to be statically-determined and included in the bundle. So instead of this:

let translation = await import(`translations/${language}.json`)

You could just do something like this instead:

let translation = await {
  'en-US': () => import(`translations/en-US.json`),
  'zh-CN': () => import(`translations/zh-CN.json`),
  // ...
}[language]()

@evanw
Copy link
Owner

evanw commented Jun 21, 2020

I'm going to close this issue "won't fix" as explained in my post above.

@moos
Copy link

moos commented Oct 27, 2020

Unfortunately the suggested work-around doesn't scale for larger (legacy) code bases where a dir substructure must be loaded. Webpack has variable dynamic imports and Parcel has the super useful glob import:

import foo from "/assets/*.png";

(but Parcel has other shortcomings, namely handling externals).

I suppose one could run a glob preprocessor and generate statically linked imports -- but having it in the tool makes it a lot easier. Or as a plugin, when that's available.

@nettybun
Copy link

@moos I think the plugin API will fit this beautifully. You'll be able to catch the "*" and return an object with anything you like

@kalvenschraut
Copy link

Not sure if anyone else has come up with a better solution, but I was able to create a plugin that will look for dynamic imports that have a template literal and a variable inside. Transform that variable into a glob i.e. /foo/bar/${baz}.vue -> /foo/bar/**/*.vue. Check the FS using fast-glob for all possible files matching that glob and generate static imports for those files. Which then the statically imported modules would be referenced by the dynamic imports instead.

I was just able to get it working for my pretty large code base and am working on cleaning up the code. Seems like my plugin accomplishes what is talked about in this issue. I can check back in when I get it published if anyone is interested.

@AndrewBogdanovTSS
Copy link

@kalvenschraut could you share your plugin?

@kalvenschraut
Copy link

kalvenschraut commented Jan 5, 2022

@kalvenschraut
Copy link

@AndrewBogdanovTSS See above comment if you have not seen it yet

@jcompagner
Copy link

its not completely clear to me is:

let translation = await import(`translations/${language}.json`)

working at runtime? if i would copy the files in that translations directory myself?

because doing it statically is a nogo for us then we never are able to use esbuild for building because in our current product that would mean 1271 lines of code....
(and that could suddenly be more if 3rd party stuff like angular,uppy,numbro and other components would suddenly add more language or locale files that i then constantly have to track)

currently webpack build does copy all the dynamic files (1271 for our current product) and i can use that dynamic import
i can live with esbuild not copy it for me, i can do that manually somewhere as long as it then works at runtime.....

@jcompagner
Copy link

Here it is

https://github.com/RtVision/esbuild-dynamic-import

i see you make it static imports what does that exactly mean?
they are still lazy loaded when needed right? and only the js/json file is loaded at runtime that i ask for?

@kalvenschraut
Copy link

No they are still imports at being added at the top of the file which was fine for my use case at the time. In theory I think it would work to make them dynamic imports of the specific file but I haven't tested it. Main thing that esbuild support IIRC is the template literal parse so if the plugin were to enumerate all the options as dynamic imports instead of static ones at the top then hopefully would work.

Vite does something similar internally, they have option to preload the imports or still keep them lazy. Though this is more so when using globs, which is very similar to how these template literals work anyway on the bundler side. They add a method that you call that they then compile down. import.meta.glob.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants