11ty / eleventy-plugin-vite

A plugin to use Vite with Eleventy
135 stars 10 forks source link

Unable to generate images with eleventy-img at build time #41

Closed ThePeach closed 2 months ago

ThePeach commented 1 year ago

The current website I have uses Nunchucks as templating language, md for posts, and that's mostly it. The config is quite straightforward for a blog. I've taken most of the configuration for it from the project Eleventy Plus Vite.

// .eleventy.js
const markdownIt = require('markdown-it')
const markdownItAnchor = require('markdown-it-anchor')

const EleventyPluginNavigation = require('@11ty/eleventy-navigation');
const EleventyPluginRss = require('@11ty/eleventy-plugin-rss');
const EleventyPluginSyntaxhighlight = require('@11ty/eleventy-plugin-syntaxhighlight');
const EleventyPluginTimeToRead = require('eleventy-plugin-time-to-read');
const EleventyVitePlugin = require('@11ty/eleventy-plugin-vite');
const rollupPluginCritical = require('rollup-plugin-critical').default;
const { eleventyImagePlugin } = require("@11ty/eleventy-img");

const filters = require('./utils/filters.js');
const transforms = require('./utils/transforms.js');
const shortcodes = require('./utils/shortcodes.js');

const { resolve } = require('path')

module.exports = function (eleventyConfig) {
  // Plugins
  eleventyConfig.addPlugin(EleventyPluginNavigation);
  eleventyConfig.addPlugin(EleventyPluginRss);
  eleventyConfig.addPlugin(EleventyPluginSyntaxhighlight);
  eleventyConfig.addPlugin(EleventyPluginTimeToRead);
  eleventyConfig.addPlugin(eleventyImagePlugin, {
    // NOTE: this config options don't actually do anything. See shortcodes.
    // Set global default options
    formats: ["webp", "jpeg"],
    urlPath: "/images/",
    outputDir: './_site/images/',
    // Notably `outputDir` is resolved automatically
    // to the project output directory
    defaultAttributes: {
      loading: "lazy",
      decoding: "async"
    }
  });
  eleventyConfig.addPlugin(EleventyVitePlugin, {
    tempFolderName: '.11ty-vite', // Default name of the temp folder

    // Vite options (equal to vite.config.js inside project root)
    viteOptions: {
      publicDir: 'public',
      clearScreen: false,
      server: {
        mode: 'development',
        middlewareMode: true,
      },
      appType: 'custom',
      assetsInclude: ['**/*.xml', '**/*.txt'],
      build: {
        mode: 'production',
        sourcemap: 'true',
        manifest: true,
        // This puts CSS and JS in subfolders – remove if you want all of it to be in /assets instead
        rollupOptions: {
          output: {
            assetFileNames: 'assets/styles/main.[hash].css',
            chunkFileNames: 'assets/scripts/[name].[hash].js',
            entryFileNames: 'assets/scripts/[name].[hash].js'
          },
          plugins: [rollupPluginCritical({
              criticalUrl: './_site/',
              criticalBase: './_site/',
              criticalPages: [
                { uri: 'index.html', template: 'index' },
                { uri: 'archive/index.html', template: 'archive' },
                { uri: '404.html', template: '404' },
              ],
              criticalConfig: {
                inline: true,
                dimensions: [
                  {
                    height: 900,
                    width: 375,
                  },
                  {
                    height: 720,
                    width: 1280,
                  },
                  {
                    height: 1080,
                    width: 1920,
                  }
                ],
                penthouse: {
                  forceInclude: ['.fonts-loaded-1 body', '.fonts-loaded-2 body'],
                }
              }
            })
          ]
        }
      }
    }
  });

  // Filters
  Object.keys(filters).forEach((filterName) => {
    eleventyConfig.addFilter(filterName, filters[filterName])
  });

  // Transforms
  Object.keys(transforms).forEach((transformName) => {
    eleventyConfig.addTransform(transformName, transforms[transformName])
  });

  // Shortcodes
  Object.keys(shortcodes).forEach((shortcodeName) => {
    eleventyConfig.addShortcode(shortcodeName, shortcodes[shortcodeName])
  });

  // front matter options (More separator)
  eleventyConfig.setFrontMatterParsingOptions({
    excerpt: true,
    excerpt_separator: "<!-- more -->"
  });

  // Customize Markdown library and settings:
  let markdownLibrary = markdownIt({
    html: true,
    breaks: true,
    linkify: true
  }).use(markdownItAnchor, {
    permalink: markdownItAnchor.permalink.ariaHidden({
      placement: 'after',
      class: 'direct-link',
      symbol: '#',
      level: [1, 2, 3, 4]
    }),
    slugify: eleventyConfig.getFilter('slug')
  });
  eleventyConfig.setLibrary('md', markdownLibrary);

  // re-enabling indendted code in MD parsing
  eleventyConfig.amendLibrary("md", mdLib => mdLib.enable("code"));

  // Layouts
  eleventyConfig.addLayoutAlias('base', 'base.njk');
  eleventyConfig.addLayoutAlias('post', 'post.njk');
  eleventyConfig.addLayoutAlias('page', 'page.njk');

  // Copy/pass-through files
  eleventyConfig.addPassthroughCopy("public");
  eleventyConfig.addPassthroughCopy('src/scripts');
  eleventyConfig.addPassthroughCopy('src/styles');

  eleventyConfig.addCollection("posts", require("./_11ty/getPosts"));

  return {
    templateFormats: ['md', 'njk', 'liquid'],
    htmlTemplateEngine: 'njk',
    passthroughFileCopy: true,
    dir: {
      input: 'src',
      // better not use "public" as it's used by vite for static assets
      output: '_site',
      includes: '_includes',
      layouts: 'layouts',
      data: '_data'
    }
  };
}

I am using njk shortcodes to generate the images.

// utils/shortcodes.js
const Image = require("@11ty/eleventy-img");
const urlPath = '/images/';
const baseImagePath = "./src/images/";
const outputDir = "./_site/images/";

module.exports = {
  year: function() { 
    return `${new Date().getFullYear()}`
  },
  image: function(src, alt, cls, sizes, widths) {
    let options = {
      widths: [300, 600, 1200],
      formats: ['jpeg', 'webp'],
      urlPath,
      outputDir
    };

    // generate images, while this is async we don’t wait
    Image(`${baseImagePath}${src}`, options);

    let imageAttributes = {
      class: cls,
      alt,
      sizes,
      loading: "lazy",
      decoding: "async",
    };
    // get metadata even if the images are not fully generated yet
    let metadata = Image.statsSync(`${baseImagePath}${src}`, options);
    return Image.generateHTML(metadata, imageAttributes);
  },
  metaImageURL: function(src) {
    const options = {
      widths: [1200],
      formats: ["jpeg"],
      urlPath,
      outputDir
    };

    Image(`${baseImagePath}${src}`, options);

    let metadata = Image.statsSync(`${baseImagePath}${src}`, options)

    let data = metadata.jpeg[metadata.jpeg.length - 1];
    return data.url;
  }
};

Now when run in dev mode, the images are generated and copied without problems in _site/images/, but when I run the build process, the _site/images/ folder does not contain any of the images that should have been generated.

Any help would be greaty appreciated, unfortunately I don't know Vite in depth enough to pester around with the config without making a mess 🙂

ngdio commented 1 year ago

By default (and judging from your settings this shouldn't have changed) Vite should put your images in the _site/assets/ subfolder along with your scripts and stylesheets. They're only present at _site/images/ since the dev server uses Vite as a middleware, meaning it works in place and doesn't discard what eleventy generates, but the images should be present in both folders.

Vite should recognise the reference to your images in html and change it to link to assets automatically in the final output. Is that not the case for you?

ThePeach commented 1 year ago

Well that was interesting, and silly me for not checking earlier... I checked the generated HTML and now I'm more confused than before, what would normally be:

    <picture><source type="image/jpeg" srcset="/images/rpql6wqYYO-300.jpeg 300w, /images/rpql6wqYYO-600.jpeg 600w, /images/rpql6wqYYO-1200.jpeg 1200w" sizes="(min-width: 600px) 600px, `1200px"><source type="image/webp" srcset="/images/rpql6wqYYO-300.webp 300w, /images/rpql6wqYYO-600.webp 600w, /images/rpql6wqYYO-1200.webp 1200w" sizes="(min-width: 600px) 600px, `1200px"><img class="" alt="An image of thick ropes laying on the ground next to each other - Photo Credit: Matteo Pescarin" loading="lazy" decoding="async" src="/images/rpql6wqYYO-300.jpeg" width="1200" height="520"></picture>

when building I get instead:

    <picture><source type="image/jpeg" srcset="/assets/styles/main.62fd7692.css 300w, /assets/styles/main.6878b93c.css 600w, /assets/styles/main.1825ebd2.css 1200w" sizes="(min-width: 600px) 600px, `1200px"><source type="image/webp" srcset="/assets/styles/main.9312b2c5.css 300w, /assets/styles/main.f99bff27.css 600w, /assets/styles/main.60027b5b.css 1200w" sizes="(min-width: 600px) 600px, `1200px"><img class="" alt="An image of thick ropes laying on the ground next to each other - Photo Credit: Matteo Pescarin" loading="lazy" decoding="async" src="/assets/styles/main.62fd7692.css" width="1200" height="520"></picture>

if I was unsure before, now I'm completely at loss. :disappointed:

I relation to your point: no, when the images are created, are only present in _site/images/.

ngdio commented 1 year ago

Can only guess but I think what causes this is your rollupOptions. Images are also assets, so assetFileNames: 'assets/styles/main.[hash].css' would cause all of them to be renamed to css files, which they're not. I don't know how to output stylesheets and images in different folders using Rollup, but all of them should be output and linked correctly into the assets folder if you remove that part from your configuration.

Alternatively, you could try and have Vite copy the images without any renaming by outputting them in the public subfolder, though you would probably need a custom function in place of Image.generateHTML, as images would need to be linked to the root folder instead of the subfolder for static assets.

KiwiKilian commented 2 months ago

As @ngdio pointed out, the problem here is the misconfiguration of assetFileNames. A proper configuration for your use case might look like this:

assetFileNames: (assetInfo) => {
          const extension = path.extname(assetInfo.name);

          if (['.jpeg', '.png', '.avif', '.webp'].includes(extension)) {
            return 'assets/images/[name].[hash].[ext]';
          }

          if ('.css' === extension) {
            return 'assets/styles/[name].[hash].[ext]';
          }

          return 'assets/[name].[hash].[ext]';
        },

For best performance @11ty/eleventy-img should output the images to the same path. So the caching of the plugin can properly work. For that usecase one would probably also omit the [hash] token for the image assets.

If the problem still persists for you, feel free to comment. Closing for now as it's not a bug of the plugin.