Suppose we have mark-up like this (multiple tbody, I know).
<tbody repeat.for="order of orders">
<tr repeat.for="line of order.lines">
<td>
<img if.bind="order.urgent === 'T'" src="../app/alert.svg">
<img if.bind="line.outOfSquare" src="../app/oos.svg">
</td>
<td class="min-width">
<img src.bind="'../app/'+line.type+'.svg'" alt="${line.type}">
</td>
</tr>
</tbody>
In a default project created by dotnet new Aurelia
the images are in-lined as DataUrls because they are small. This would be reasonable but they are repeated in many rows according to the bound data. Tweaking webpack.config.js to drop the threshold to 1024 bytes, we have
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=1024' }
and now the images appear with hashed names in wwwroot/dist, and the URLs are rewritten. The computed URL targets are also bundled by this addition to webpack.config.js
,new GlobDependenciesPlugin({
"boot": [
"ClientApp/app/components/**/*.svg"
]
})
Unfortunately, computed URLs are not rewritten.
src.bind="'../app/'+line.type+'.svg'"
and they are now broken.
We need to resolve this at run-time, but thus far I cannot find any support for doing so. Various possibilities have been suggested:
My own research reveals that there are webpack plugins that emit these mappings as json, but my shallow understanding of the Aurelia build process does not allow me to exploit this -- apart from anything else I don't know how to cause the output of this to be made available to the application.
This seems relevant but ignorance hampers me. How to load image files with webpack file-loader
My attempt to use require did not work, but I suspect that the require method that is automatically in scope in an Aurelia module is not the Webpack require that might resolve the mapping. Presumably webpack is available at runtime to load and decode the packed application, but I don't really know because up till now it has just worked, allowing me to operate in blissful ignorance.
I am aware that I can embed this into the page by handling each line type separately with a static reference to the resource, like this:
<img if.bind="line.type === 'AL'" src="../app/al.svg">
<img if.bind="line.type === 'GD'" src="../app/gd.svg">
but this is high maintenance code.
Another possibility is to go the other way. Borrowing from the suggestion to place a hidden div full of imgs, if these are all in-lined then it may be possible to copy the image with binding.
With require.context
you can tell webpack to bundle all files that match a certain pattern, and then use the created context to dynamically resolve their paths at runtime.
Let's say you have a folder src/assets/images
with all your images in it. One of the images is named image-1.jpg
. You could create a file src/images.js
like this:
const imageContext = require.context(
".",
true,
/^\.\/.*\.(jpe?g|png|gif)$/i
);
export { imageContext };
This context will (recursively) include all images under src
. Then in, for example app.js
, import and use that to resolve your images:
import { imageContext } from "./images";
export class App {
constructor() {
this.img = imageContext("./assets/images/image-1.jpg");
// resolved: '/src/assets/images/image-1.f1224ebcc42b44226aa414bce80fd715.jpg'
}
}
<img src.bind="img">
Note: the path you pass to the context to resolve your images must be relative to the path you passed to the context when you declared it. So if you had a src/assets/images.js
like this:
const imageContext = require.context(
"./images",
true,
/^\.\/.*\.(jpe?g|png|gif)$/i
);
export { imageContext };
Then in src/app.js
you'd do this:
import { imageContext } from "./images";
export class App {
constructor() {
this.img = imageContext("./image-1.jpg");
// resolved: '/src/assets/images/image-1.f1224ebcc42b44226aa414bce80fd715.jpg'
}
}
It gets even easier if you create a context per group of images that you want to display together:
src/pages/album/album.js:
const ctx = require.context(
"../../assets/images",
true,
/^\.\/.*some-special-pattern.*\.(jpe?g|png|gif)$/i
);
export class Album {
constructor() {
this.images = ctx.keys().forEach(imageContext);
}
}
<img repeat.for="img of images" src.bind="img">
Create a ValueConverter for example:
src/resources/converters/image-context.js:
const imageContext = require.context(
"../../",
true,
/^\.\/.*\.(jpe?g|png|gif)$/i
);
export class ImageContextValueConverter {
toView(name) {
const key = imageContext.keys().find(k => k.includes(name));
return imageContext(key);
}
}
src/resources/index.js
import { PLATFORM } from "aurelia-pal";
export function configure(config) {
config.globalResources([
PLATFORM.moduleName("resources/converters/image-context")
]);
}
Then anywhere else, e.g. to get src/assets/images/image-1.jpg:
<img src.bind="'image-1' | imageContext"
>
When I tried to do this in my project I ran into issues with url-loader
and I couldn't get it to work. Feels like url-loader is broken with the latest webpack version since it also ignores the limit
option. I had to completely toss out url-loader for processing images. I'll update my answer if/when I get that to work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With