Search code examples
preprocessorsvelterolluprollupjssapper

Change the order of preprocessors in Svelte / Sapper


I am having quite a big problem in my sapper app. I am using rollup with the svelte-preprocess plugin to convert my scss to css:

const preprocess = sveltePreprocess({
  scss: {
    data: `@import '${join(process.cwd(), "src/styles/main.scss")}';`,
    includePaths: ["node_modules", "src"],
  },
  postcss: {
    plugins: [...],
  },
});

After that, I want to run another preprocessor, svelte-image to optimize my images. The problem here is that as per design of the preprocessors, ones that effect markup will always run first. This will cause my image preprocessor to fail, as he will encounter scss files and won't be able to pass them.

The best solution for me would be to write my own preprocessor, which does the styling and then calls the image library. There are some examples in the docs, but I don't really understand them. If I try this example:

const svelte = require('svelte/compiler');

const { code } = await svelte.preprocess(source, [
    {
        markup: () => myPreprocess()
        style: () => imagePreprocess()
    }
], {
    filename: 'App.svelte' // Which file would that be for sapper?
});

I get an error that source is unknown. So can anybody point me in the right direction how to right and call that specific preprocessor? Or maybe to a better solution ;-)


Solution

  • tl;dr

    I got partway through explaining how to do this and realised it would make a useful utility. I've created a package to handle this: svelte-sequential-preprocessor.

    // rollup.config.js
    import svelte from 'rollup-plugin-svelte';
    import seqPreprocessor from 'svelte-sequential-preprocessor'
    import autoPreprocess from 'svelte-preprocess'
    import image from 'svelte-image'
     
    export default {
      ...,
      plugins: [
        svelte({
          preprocess: seqPreprocessor([ autoPreprocess(), image() ])
        })
      ]
    }
    

    Explanation

    The preprocess usage example in the docs is for using the preprocessor function as a standalone utility. To use a custom preprocessor with rollup you would do something like this:

    export default {
      ...,
      plugins: [
        svelte({
          preprocess: {
            markup: ({ content, filename }) => myPreprocess(content)
          }
        })
      ]
    }
    

    Note: The content passed to markup() will be a full svelte file represented as a string.

    However, even after taking this into consideration, your proposal wouldn't work in the way you described it. The problem is the style preprocessors only get the styles as content. svelte-image needs the full Svelte component which is what markup() gets.

    If you make one final adjustment, you can make it work. Instead of trying to call the preprocessors at different stages, you can force each preprocessor to run through all the stages before invoking the next, by using the preprocess function from the library you referenced above. A complete example can be found here but here's a basic outline of the logic.

    export default {
      ...,
      plugins: [
        svelte({
          preprocess: {
            markup: async ({ content, filename }) => {
              const processed = await svelte.preprocess(content, autoPreprocess({ options }), { filename });
              // Handle return value and repeat for other preprocessors
              return {
                code: ...,
                dependencies: ...
              };
          }
        })
      ]
    }