tighten / jigsaw

Simple static sites with Laravel’s Blade.
https://jigsaw.tighten.com
MIT License
2.13k stars 181 forks source link

Confusion about image handling #432

Closed GenieTim closed 4 years ago

GenieTim commented 4 years ago

Thank you for this nice site generator! I do have a question/proposal/confusion about how to best handle images with Jigsaw. I will make a PR for the docs in case my confusion is understandable.

So, my understanding: By default, jigsaw copies images (and other stuff) in assets/*, say assets/images 1:1 to the output directory (say, build_production) upon build, no further processing. So far so good.

Following problem 1: I am working on a more complex, bigger site/blog kind of thing. I will have many posts with many images. To have an easier management, I would like to group them together: say, a directory source/_posts/2020/my_post with an index.md and tons of images in the same directory. The images are used by this index.md. This does not work: Jigsaw fails with error(s) CollectionDataLoader.php line 106: No matching collection item handler for file: whatever_image.jpg. Would you consider an image item handler (copying, possibly compressing the images) a valid proposal for a PR, do you insist on the design decision to keep the images in a totally separate directory, or am I totally missing something/doing something wrong?

Following problem 2: Accepting that images are put in assets/images, I want to automatically compress & resize them. It is no problem to change the links in markdown files using event listeners (see also #357 ) and having a blade img templated included for blade files (e.g. like this:

<img alt="{{$alt}}" src="{{$page->baseUrl}}/assets/images/{{ $src }}.jpg" srcset="
@php
    $sizes = [150, 300, 600, 900, 1200];
    foreach ($sizes as $i => $size) {
      echo $page->baseUrl . "/assets/build/source/assets/images/" . $src . "-" .$size . ".jpg " . $size . "w"; 
      if ($i < (count($sizes) - 1)) { echo ", "; } 
    } 
@endphp " sizes="(max-width: 750px) 95vw, 30vw" />

, invoked like

@include('_components.img', ['alt' => $page->title . 'cover image', 'src' => $page->cover_image ])

.). What is not clear to me is where to hook for the actual image sizing. In the event listener does not work because of #172 . When hooking up webpack by adding laravel-mix-imagemin and responsive-loader in webpack.mix.js like so:

let mix = require('laravel-mix');
let build = require('./tasks/build.js');
const CopyWebpackPlugin = require('copy-webpack-plugin');
require('laravel-mix-imagemin');

mix.webpackConfig({
    plugins: [
        build.jigsaw,
        build.browserSync(),
        build.watch([
            'config.php',
            'source/**/*.md',
            'source/**/*.php',
            'source/**/*.scss',
        ]),
       // needed to prevent need for `require.context("./../../assets/images/", true, /^\.\/.*\.(jpe?g|png)/);` in main.js
       // which would be a performance neck
       // (but does not influence the final effect of getting too many images)
        new CopyWebpackPlugin([
            { from: 'source/assets/images', to: 'images' }
        ]),
    ],
    module: {
        rules: [
            {
                test: /\.(jpe?g|png)$/i,
                loader: 'responsive-loader',
                options: {
                    adapter: require('responsive-loader/sharp'),
                    sizes: [150, 300, 600, 900, 1200],
                    name: "[name]-[width].[ext]"
                },
            }
        ]
    }
});

mix.js('source/_assets/js/main.js', 'js')
    .imagemin('source/assets/images/**/*.*',
        {
            from: "**/*",
            to: "[name].[ext]"
        }, {}
    )
    .sourceMaps()
    .sass('source/_assets/sass/main.scss', 'css/main.css')
    .sourceMaps()
    .version();

I get tons of image files at tons of different locations: source/assets/images/, source/assets/build/, source/assets/build/images/, source/assets/build/source/assets/images/. The same images in all of those directories, except for the directory source/assets/build/ where I additionally can find the resized ones. Why do I produce such a mess? Is Jigsaw itself copying some images somewhere too during the build process? Can I hook up there? The problem with having the mess is that it takes longer, uses more storage and is confusing. In any case, a workaround would be to script the resizing & compression up separately so that I do not have to worry about any webpack interference, but it feels kindof wrong – any best practices I missed?

damiani commented 4 years ago
  1. For now, this is probably difficult to do because of the way Jigsaw parses collection items and builds output paths. But I'm revamping that for the next major release (2.0), and I definitely want to add support for this in that version.

  2. Jigsaw doesn't do any image handling of its own, so it would all be handled in Webpack via Mix or whatever plugins you add. Assuming your images are in source/assets/images, and your public path in Mix is set to source/assets/build (which it is by default in Jigsaw, with mix.setPublicPath('source/assets/build/')), then the following works for me:

let mix = require('laravel-mix');
let build = require('./tasks/build.js');
require('laravel-mix-imagemin');

mix.disableSuccessNotifications();
mix.setPublicPath('source/assets/build/');
mix.webpackConfig({
    plugins: [
        build.jigsaw,
        build.browserSync(),
        build.watch([
            'config.php',
            'source/**/*.md',
            'source/**/*.php',
            'source/**/*.scss',
        ]),
    ],
    module: {
        rules: [
            {
                test: /\.(jpe?g|png)$/i,
                loader: 'responsive-loader',
                options: {
                    sizes: [150, 300, 600, 900, 1200],
                    name: "[name]-[width].[ext]",
                    outputPath: 'images/sizes',
                    adapter: require('responsive-loader/sharp')
                },
            }
        ]
    },
});

mix.js('source/_assets/js/main.js', 'js')
    .sass('source/_assets/sass/main.scss', 'css/main.css')
    .sourceMaps()
    .imagemin({
        from: 'source/assets/images/**/*.*',
        to: 'images/[name].[ext]',
    })
    .version();

... with the following in main.js (which is necessary, as far as I can tell, in order to get Webpack to do anything with the images):

require.context("./../../assets/images/", true, /^\.\/.*\.(jpe?g|png)/);

Note that your imagemin didn't seem to be set up correctly; in my testing, the first parameter, not the second, should be the file patterns. And responsive-loader needed the outputPath: 'images' option.


With this setup, Webpack will:

After Webpack is done, Jigsaw will copy your whole assets folder to build_*, and your pages should link to images that are in assets/build/images.


NOTE: After you get this set up, I would delete your /source/assets/build directory before running npm run dev the first time. I suspect that the duplicated files are old ones lying around from previous runs while you were testing different paths, which don't get deleted.

michapixel commented 4 years ago

@damiani i wonder what this line holds: let build = require('./tasks/build.js'); what is the folder tasks (does it have any special meaning) and what is in that js?

huglester commented 3 years ago

Hello

is there a sollution for this? I tried options from comments but did not work for me.

Maybe there is some dev-branch with imagemin implemantation?

thanks

GenieTim commented 3 years ago

@huglester the code of my blog with working image processing can be found in https://github.com/GenieTim/genieblog.ch, if that helps