Search code examples
webpacksassnode-sasssass-loadercopy-webpack-plugin

How to export sass files to dist folder preserving folder structure?


I'm trying to take all the sass files in a particular folder, and copy/transform them into css files in a different folder, preserving the folder structure. Like:

  • src
    • folder1
      • test1.scss
    • folder2
      • test2.scss
      • folder3
        • test3.scss

into

  • dest
    • folder1
      • test1.css
    • folder2
      • test2.css
      • folder3
        • test3.css

I'm trying to use copy webpack plugin to copy all the src scss files, and the transform option to use node-sass to convert them, which seems to work, except it won't rename the files to .css and i can't figure how to fix this. Any ideas?

snip from my webpack.config.js:

{
      context: path.join(__dirname, "src"),
      from: "**/*.scss",
      to: path.join(__dirname, "dest"),
      transform(content, path) {
        const result = sass.renderSync({ file: path });
        return result.css.toString();
      }
    }
        

Solution

  • Transforming files by using webpack-copy-plugin is not really a right way to process your assets. It is more like hackish/anti-pattern way. Instead you, should rely on proper loader pipeline. Webpack loaders are especially designed to handle this task. You need 4 things to accomplish you task.

    • For processing SASS/SCSS files, you should use sass-loader.
    • Use css-loader to convert CSS into CommonJS format.
    • Augment your config with mini-css-extract-plugin to extract CSS from JS code into actual CSS file.
    • Use the object syntax to specify your SASS target files individually. This is the key to preserving your folder structure in dist folder. The key of the entry object acts like a folder structure within dist/dest. (See below)

    Your configuration would look like:

    const path = require('path');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
    module.exports = {
    
        output: {
            // Whatever name pattern you need.
            filename: `[name]/bundle-[contenthash].js`,
            path: path.resolve(__dirname, 'dest')
        },
    
        entry: {
            'folder1': './src/folder1/test1.scss',
            'folder2': './src/folder2/test2.scss',
            'folder2/folder3': './src/folder2/folder3/test3.scss'
        },
    
        modules: {
            rules: [
                {
                    test: /\.(sa|sc|c)ss$/,
                    use: [
    
                        // Mark content for extraction into seperate CSS file
                        MiniCssExtractPlugin.loader,
    
                        // Translates CSS into CommonJS
                        'css-loader',
                        
                        // Compiles Sass to CSS
                        'sass-loader'
                    ]
                }
            ]
        },
    
        plugins: [
            // Run the actual plugin to extract CSS into separate CSS files
            new MiniCssExtractPlugin({
                // Whatever name pattern you need
                filename: `[name].css`
            })
        ]
    
    };
    

    Adjust your filename with the patterns you need.

    Alternately, if you wish to stick to the same approach, the to parameter of copy plugin configuration accepts the webpack style output patterns. Something similar can work for you:

    // You can use .css instead of [ext]
    to: '[path][name].[contenthash].[ext]'
    

    Finally, as one last option, if there are too many SASS files, then you can dynamically construct the Webpack entry object using glob package and then using that in your module configuration.