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

Add require.context.keys-like feature to import #7283

Closed
fatfisz opened this issue May 12, 2018 · 16 comments
Closed

Add require.context.keys-like feature to import #7283

fatfisz opened this issue May 12, 2018 · 16 comments

Comments

@fatfisz
Copy link

fatfisz commented May 12, 2018

Feature request

What is the expected behavior?
I'd like webpack to set a specific id for the chunk generated for dynamic imports, something similar to this:

// multiple possible targets
import(
  /* webpackId: "some-id" */
  `./locale/${language}`
);

After that I could refer to the generated keys through an appropriate require call (I'm new to tinkering with webpack, so I'm not yet sure if require.cache is ok or not).

What is motivation or use case for adding/changing the behavior?
Importing multiple files is supported in webpack through e.g. require.context and import.

import:

  • Allows lazy loading and generates chunks for each module
  • Generates a map of all paths, but getting access to it is problematic (I could iterate over all require.cache entries and try to get the right one, but feels hacky)

require.context:

  • Exposes keys of all loaded modules
  • Includes everything in one chunk and is not lazy

I want to get the list of all matched files (pages) so that I can reference them in the code of a navigation component.

I'd like this to be handled by webpack. After all, the "keys" method is there in the code, it's just not accessible like in the case of require.context.

An alternative way would be to use a babel plugin or a macro like import-all.macro, but then the plugin would not be pure and that makes caching hard.

It seems that a few other people want a feature like this: #4807 (comment)

How should this be implemented in your opinion?

Reusing the existing feature of magic comments would be a good way to achieve that IMO.

Are you willing to work on this yourself?
Of course!

@sokra
Copy link
Member

sokra commented May 12, 2018

Includes everything in one chunk and is not lazy

There is a mode parameter to require.context which also allows behavior like import().

@fatfisz
Copy link
Author

fatfisz commented May 12, 2018

Thanks for the info! I looked hard again and I couldn't find any documentation for this option though.

I managed to find the webpack/lib/dependencies/RequireContextDependencyParserPlugin.js file, so I'll start learning from there.

If you were interested in extending import anyway, I'd be happy to help!

@sokra
Copy link
Member

sokra commented May 13, 2018

If you were interested in extending import anyway, I'd be happy to help!

If you propose a good way/syntax to do that while being spec-compliant that would be a start.

@caseyWebb
Copy link

@sokra Could you provide an example of using the mode option with require.context?

@fatfisz
Copy link
Author

fatfisz commented May 15, 2018

I ended up using this:

const importDocs = require.context('..', true, /\.docs\.js/, 'lazy');

importDocs.keys(); // sync, return an array of gathered paths

await importDocs(path); // async, resolves to the module

It's the fourth argument. The possible values seem to be: 'sync', 'eager', 'weak', 'async-weak', 'lazy', 'lazy-once' (as found here).

@fatfisz
Copy link
Author

fatfisz commented May 17, 2018

I'm closing, since I don't have a better idea than adding a magic comment. I switched to require.context anyway.

@fatfisz
Copy link
Author

fatfisz commented May 22, 2018

I'd like to reopen, since require.context and import are quite incompatible. I'm still interested in a solution that wouldn't require me to use require.context.

My problem is that while I can get the list of all paths with require.context, like so:

const weakDocs = require.context('__cwd', true, /\.docs\.js/, 'weak');

I still need to wrangle with the fact that import(`__cwd/${path}.docs.js`) can't use one of the paths from weakDocs without a special treatment. After all, paths from weakDocs already include __cwd and .docs.js, so they'd be included twice when used inside import. Right now just to move forward I came up with a hack which is based on adding marker around the path and using the fact that webpack is not resolving constants staticly:

const marker = '|';
import(`__cwd/${marker}${path}${marker}.docs.js`).then(useModule);

For this to work I have to modify the webpackAsyncContext auto-generated code (through a webpack plugin). But of course it's far from how I want my code to look like. Right now I'm stuck with import, because it's the only way to set names for the chunks that are generated (unless I missed something).

@sokra You wrote about being spec-compliant - are you referring to the ECMAScript spec? AFAIK there's nothing like "magic comments" there, so I was thinking that they could be utilized somehow. Do you maybe have any suggestions as to what syntax would be good in mind? Is extending import, e.g. import.context in any way acceptable?

@fatfisz fatfisz reopened this May 22, 2018
@fatfisz
Copy link
Author

fatfisz commented May 23, 2018

So the problem with an expression like import(`foo${bar}`) is that it needs to have bar in the scope. require.context doesn't suffer from that. Based on that I propose three solutions:

  1. Allowing an argument in require.context that would be the chunk name seems like an obvious solution, but AFAIK extending require.context is a no-no - or is it?

  2. Adding import.context that would be almost exactly like require.context with an exception of having "lazy" as the default mode.

  3. import.glob(/* magic comments */ 'foo/**/*.js') that returns a function like require.context. This also avoids import being dependent on another expression.

Please let me know if those are any good. If not, could I ask for some feedback on why those solutions are not ok?

@sokra
Copy link
Member

sokra commented May 25, 2018

const importDocs = require.context('..', true, /\.docs\.js/, 'lazy');

importDocs.keys(); // sync, return an array of gathered paths

await importDocs(`./${path}.docs.js`); // async, resolves to the module

Does this work?


import.anything is not valid syntax except for import.meta since import is a keyword.

@fatfisz
Copy link
Author

fatfisz commented May 25, 2018

It works (with await importDocs(path)), but I can't set the chunk name for require.context, which I need to do badly. Is adding a 5th argument with the chunk name to require.context an option?

@fatfisz
Copy link
Author

fatfisz commented Aug 30, 2018

In the meantime I learned a lot about how Webpack works and the final solution that I came up with was to add a custom loader that generates the code that I need. To ensure that caching and hot reloading work, the loader uses the this.addContextDependency method.

I still think that having a built-in solution would be better, but as I was able to solve my original problem in a reasonable way, I'm closing this issue.

@fregante
Copy link

fregante commented Jan 8, 2019

@fatfisz what loader are you using? Can you share it (or its code)? I only found this and the dependencies aren't added: https://github.com/terpiljenya/import-glob

Even this babel plugin doesn't help: https://github.com/novemberborn/babel-plugin-import-glob

@fatfisz
Copy link
Author

fatfisz commented Jan 9, 2019

@bfred-it Here's my code: https://github.com/Codility/burdoc/blob/master/burdocImportsLoader.js

The gist of it is I have a code like this: const burdocDocsImports = BURDOC_DOCS_IMPORTS; and then the loader swaps BURDOC_DOCS_IMPORTS with whatever imports I need (I run glob to get the files) and then I add directories I need as context dependencies.

Admittedly I could've done a better job with this.cacheable because if I understand correctly now everything is rebuilt on each change, but for now it works. I'll be improving the code in the future anyway.

@spd789562
Copy link

Does require.context lazy mode can add prefetch commit?
I'm try req(/* webpackPrefetch: true */ path) but it doesn't work.

@BlueNebulaDev
Copy link

require.context is no longer available in javascript modules:

ReferenceError: require is not defined

Is it possible to get a list of all the files that could be loaded with an import expression (for instance import(`./components/${component}.js`)) like it was possible with require.context's keys()?

@chrisspiegl
Copy link

@BlueNebulaDev I am wondering the same thing, did you find a solution?

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

7 participants