skip to Main Content

I want to bundle multiple scss and js entries into multiple output files using webpack.
I have tried different solutions for it, but non of them did what I needed.

This is how the directory structure looks like:

assets
   package.json
   webpack.config.js
   css
      dist
      src
         lib
         pages
         fonts.scss
         main.scss
   js
      dist
      lib
      src
         components
         pages
         App.js
         

I want to end up having the main.scss & all the scss files from the "css/src/pages" directory compiled to "css/dist/main.css" and "css/dist/pages/page-name.css".

My webpack.config.js file looks like this:

const path = require('path');
const fs = require('fs');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");



let config = {
    mode: "production",
    watch: true,
    module: {
        rules: [
            {
                test: /.scss$/i,
                include: [
                    path.resolve(__dirname, 'css/src'),
                    path.resolve(__dirname, 'css/src/pages')
                ],
                // use: [MiniCssExtractPlugin.loader, "css-loader", 'sass-loader']
                // use: ['file-loader', 'sass-loader', 'css-loader']
                use: [
                    {
                        loader : 'file-loader',
                        options: { outputPath: '/', name: '[name].css'}
                    },
                    'sass-loader'
                ]
            }
        ]
    },
    // plugins: [new MiniCssExtractPlugin()],
    // optimization: {
    //     minimize: true,
    //     minimizer: [new CssMinimizerPlugin()]
    // }
};

const main = Object.assign({}, config, {
    name: 'main',
    entry: './js/src/App.js',
    output: {
      filename: 'app.min.js',
      path: path.resolve(__dirname, 'js/dist'),
    }
});


const exp  = [main];
const jsDir = __dirname + "\js\src\pages";
const jsFiles = fs.readdirSync(jsDir);

jsFiles.forEach(fileName => {
    const nameOnly = fileName.replace(".js", "");
    const current = Object.assign({}, config, {
        name: nameOnly,
        entry: './js/src/pages/'+fileName,
        output: {
          filename: fileName,
          path: path.resolve(__dirname, 'js/dist/pages'),
        }
      });
      exp.push(current);
});



// const cssMain = Object.assign({}, config, {
//     name: 'mainCss',
//     entry: './css/src/main.scss',
//     output: {
//       filename: 'main.css',
//       path: path.resolve(__dirname, 'css/dist'),
//     }
// });

// exp.push(cssMain);

const cssDir = __dirname + "\css\src\pages";
const cssFiles = fs.readdirSync(cssDir);

cssFiles.forEach(fileName => {


    const nameOnly = fileName.replace(".scss", "");
    const current = Object.assign({}, config, {
        name: nameOnly + "Css",
        entry: './css/src/pages/'+fileName,
        output: {
          filename: nameOnly + ".css",
          path: path.resolve(__dirname, 'css/dist/pages'),
        }
    });

    exp.push(current);

});



module.exports = exp;

As you can see there are few lines that are commented out, those are part of the things I’ve tried, but non of it worked, unfortunately.

The scss files from the "css/src/pages" directory always print javascript inside like in the attached screenshot and the main.scss file is compiled well into css but not into the right directory (it goes to "css/dist/pages" too).

enter image description here

FYI – the javascript compilation works as expected.

Any help will be highly appreciated! Thank you in advance 🙂

2

Answers


  1. Chosen as BEST ANSWER

    After many hours spent on this issue, I found the solution that I was looking for.

    I'm adding here my new webpack.config.js file, in case someone will come across this type of issue too:

    const path = require('path');
    const fs = require("fs");
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const FixStyleOnlyEntriesPlugin = require("webpack-fix-style-only-entries");
    
    
    
    const exp = {
      mode: "production",
      watch: true,
      entry: {
        'css/dist/main': './css/src/main.scss',
        'js/dist/app.min': './js/src/App.js',
      },
      output: {
        filename: '[name].js',
        path: path.resolve(__dirname),
      },
      module: {
        rules: [
          {
            test: /.js$/,
            exclude: [/node_modules/, /.scss$/],
            use: 'babel-loader',
          },
          {
            test: /.scss$/,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              'sass-loader',
            ],
          },
        ],
      },
      plugins: [
        new FixStyleOnlyEntriesPlugin(),
        new MiniCssExtractPlugin({
          filename: '[name].css',
        }),
      ],
    };
    
    
    const jsDir = __dirname + "\js\src\pages";
    const jsFiles = fs.readdirSync(jsDir);
    jsFiles.forEach((fileName) => {
      const nameOnly = fileName.replace(".js", "");
      exp.entry[`js/dist/pages/${nameOnly}`] = `./js/src/pages/${fileName}`;
    });
    
    const cssDir = __dirname + "\css\src\pages";
    const cssFiles = fs.readdirSync(cssDir);
    
    cssFiles.forEach((fileName) => {
      const nameOnly = fileName.replace(".scss", "");
      exp.entry[`css/dist/pages/${nameOnly}`] = `./css/src/pages/${fileName}`;
    });
    
    
    module.exports = exp;
    
    

    And this is the package.json file:

    {
      "name": "digitaliz",
      "version": "1.0.0",
      "description": "",
      "main": "App.js",
      "scripts": {
        "dev": "webpack"
      },
      "author": "Digitaliz",
      "license": "ISC",
      "devDependencies": {
        "babel-loader": "^9.1.3",
        "css-loader": "^6.8.1",
        "mini-css-extract-plugin": "^2.7.6",
        "sass": "^1.64.1",
        "sass-loader": "^13.3.2",
        "style-loader": "^3.3.3",
        "webpack": "^5.88.2",
        "webpack-cli": "^5.1.4",
        "webpack-fix-style-only-entries": "^0.6.1"
      }
    }
    

    Hope it'll help someone :)


  2. Firstly, note that the file-loader you’re currently using processes the import/requires on a file, then emits the file into the output directory. However, this is not exactly what you need for processing SCSS/SASS files. Instead, you should use the MiniCssExtractPlugin which extracts CSS into separate files. It creates a CSS file per JS file which contains CSS.

    Here’s a way you could structure your webpack.config.js file to achieve what you’re looking for:

    const path = require('path');
    const fs = require('fs');
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    
    const getEntries = (directoryPath, extension) => {
        const entries = {};
        fs.readdirSync(directoryPath).forEach(file => {
            if (file.match(new RegExp(`.*.(${extension})$`))) { 
                entries[path.parse(file).name] = path.resolve(directoryPath, file);
            }
        });
        return entries;
    }
    
    let config = {
        mode: "production",
        watch: true,
        module: {
            rules: [
                {
                    test: /.scss$/i,
                    use: [
                        MiniCssExtractPlugin.loader,
                        "css-loader",
                        "sass-loader"
                    ]
                },
                {
                    test: /.js$/i,
                    exclude: /node_modules/,
                    use: {
                        loader: "babel-loader"
                    }
                }
            ]
        },
        plugins: [new MiniCssExtractPlugin()],
        optimization: {
            minimize: true,
            minimizer: [new CssMinimizerPlugin()]
        }
    };
    
    const scssConfig = {
        ...config,
        name: 'scss',
        entry: {
            main: './css/src/main.scss',
            ...getEntries(path.resolve(__dirname, 'css/src/pages'), 'scss')
        },
        output: {
            path: path.resolve(__dirname, 'css/dist'),
            filename: '[name].css',
        },
        plugins: [
            new MiniCssExtractPlugin({
                filename: (chunkData) => {
                    return chunkData.chunk.name === 'main' ? '[name].css' : 'pages/[name].css';
                },
            }),
        ]
    };
    
    const jsConfig = {
        ...config,
        name: 'js',
        entry: {
            app: './js/src/App.js',
            ...getEntries(path.resolve(__dirname, 'js/src/pages'), 'js')
        },
        output: {
            filename: '[name].min.js',
            path: path.resolve(__dirname, 'js/dist'),
        }
    };
    
    module.exports = [scssConfig, jsConfig];
    

    In this setup, getEntries is a helper function that gets all files with the provided extension from the provided directory and prepares the entries object for Webpack. For the SCSS config, it is specifying separate output paths for the main.scss and the other SCSS files in the ‘pages’ directory. This way, the main.css will be directly under the ‘dist’ directory, and the others under the ‘pages’ directory.

    You also might want to consider adding Babel loader for transpiling JavaScript files and setting it up in Webpack.

    Remember to install all the necessary dependencies via npm. You may also need to create ‘.babelrc’ for Babel setup and configure Babel for your JavaScript files as per your project requirements.

    For the error screenshot you provided, it seems like the output is trying to interpret CSS as JS, which happens if you’re not correctly setting up MiniCssExtractPlugin. Make sure you’re using it correctly, as shown in the provided configuration.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search