Closed wichert closed 7 years ago
That's a good question. The problem is that webpack relies on static analysis. It needs to be able to deduce what to generate without running the code.
There's a potential way you could try, though. Look up require.context
. It's covered briefly in one of the end chapters (chunk one). I believe you might be able to resolve your problem through it.
Let me know how it goes. I'll add a tip related to this issue in the book.
What I ended up doing is this: I created a directory called images
at the top level, with all my images in there. In a component (located in app/components/
) I do this:
import React from 'react';
var images = require.context('../../images', true);
class MyPage extends React.Component {
render() {
let img_src = images(`./icons/effect-${this.state.effect}.jpg`)
return (
<img src={img_src} alt=""/>
);
}
}
A couple of things to take note:
./
, which is not intuitive but that is just how the context map is populatedrequire.context
is used to include subdirectories, and defaults to false.I am not sure if it is better to create the require context in each component, or somewhere globally and include it as a singleton.
Looking good. The only gotcha is that it could be difficult to test this particular component due to that require.context
dependency, but if you are fine with that, this will work.
if you use the URL loader this will can include base64 blocks in your bundle, so you may want to create a separate bundle for those (I have no idea how to do that one so far).
What sort of output do you want? I think file-loader should help here. You can configure url-loader limit
to use file-loader instead given file size goes beyond the given value.
I am not sure if it is better to create the require context in each component, or somewhere globally and include it as a singleton.
It probably depends on how you intend to use the images. In any case hiding require.context
behind a module interface could be a good move as then you decouple your code for webpack at least a little bit. The idea is that you could replace the mechanism behind the interface later without having to change a lot of code.
I would expect a test to pull in the requirement as well, so how will that make testing more difficult?
Normally your images (and other assets) do not change as much as your code, so for the same reason you create a separate vendor bundle it probably makes sense to create a separate asset-bundle with often used assets.
I would expect a test to pull in the requirement as well, so how will that make testing more difficult?
I mean testing outside of webpack environment (shallow testing through React with Mocha and so on).
Normally your images (and other assets) do not change as much as your code, so for the same reason you create a separate vendor bundle it probably makes sense to create a separate asset-bundle with often used assets.
Yeah, that makes sense. You can probably generate a bundle like that through the ExtractTextPlugin
(same idea as for CSS).
@wichert @bebraw awesome solution, thx. I was just looking for something like that to cachebust dynamic images. Worth to be included in book.
I have expanded the chunk portion somewhat so it's probably safe to close this one.
Feel free to open more specific issues if you think there's something missing.
You should think to share static folder on backend, save your images there and reach them on http://localhost:
FYI this causes issues with Jest as the function is webpack provided.
● Test suite failed to run
TypeError: require.context is not a function
21 | import iconSavePNG from 'img/icon-save.png';
22 |
> 23 | const images = require.context('img', true);
| ^
24 |
25 | export default [
26 | {
at Object.<anonymous> (src/content/structure.js:23:24)
at Object.<anonymous> (src/components/ContentExplorer.js:11:1)
at Object.<anonymous> (src/views/App.js:6:1)
at Object.<anonymous> (src/views/App.test.js:4:1)
Code works but can't shallow render the component for tests, Jest throws a hissy.
Any solutions welcomed!
@chrisheseltine I think I would extract the const images require.context('img', true)
call to a module of its own to import and then mock it to return something that works with your tests. Here's an example: https://stackoverflow.com/a/42439030/228885 .
I tried this, the images worked still, but the test still errored with the mock. In the end I reverted back to referencing files as strings and passing them directly into tags, took a little rewriting is all.
I tried this https://github.com/survivejs/webpack-book/issues/80#issuecomment-216068406
images(imagePath).default
I had to add .default to make it work.
Other solution for those if this doesn't work,
const Image = ({ name, className, ...restProps }) => {
const src = require(`assets/images/${name}`).default;
name = name.replace('/', '-'); // For name provided as path, replace '/' with '-' for class names, otherwise / doesn't work in css of that class.
return (
<img
{...restProps}
src={src}
/>
);
};
Strangely, for me, this solution works in one of the repos but not in exactly same repo(probably some minor version change in package-lock.json). There it throws Critical dependency is an expression
in the build.
I tried this #80 (comment)
images(imagePath).default
I had to add .default to make it work.
Other solution for those if this doesn't work,
const Image = ({ name, className, ...restProps }) => { const src = require(`assets/images/${name}`).default; name = name.replace('/', '-'); // For name provided as path, replace '/' with '-' for class names, otherwise / doesn't work in css of that class. return ( <img {...restProps} src={src} /> ); };
Strangely, for me, this solution works in one of the repos but not in exactly same repo(probably some minor version change in package-lock.json). There it throws
Critical dependency is an expression
in the build.
how can we load images without ".default" at end? I mean with require("path") only should work.
@smvora4u You could use named exports. I.e.
const someAsset = '...';
export { someAsset };
Then you can do const src = require(
assets/images/${name}).someAsset
.
The .default
bit comes from the way default exports work (unfortunate legacy restriction).
I find myself needing to create
img
elements dynamically. Something likeThe image loading chapter does not tell you how to do that; it only gives an example for CSS. I naively tried using the same approach by passing a path relative from the .jsx file to the image to
require
, but that results in aError: Cannot find module
error. Since I suspect this is a common issue it would be nice to add an example for this kind of use case to the chapter.