The need for multiple output paths?

I have the following use case,
I am using React and along with that I have certain CSS files as dependencies. I have the following distribution folder tree:

-- dist/
    --js/
    --css/
    --media/

Since, now I would like to include CSS into the build system, but now since I have my output path in the webpack.config.js to ./dist/js, so even my CSS files are being generated there.

I think it is important to have multiple output paths. If not, could anybody suggest me any other workflow?

Author: Fantashit

21 thoughts on “The need for multiple output paths?

  1. Hi, this is not clear exactly how to work. Also note that I have multiple entry points so here is what my webpack.config.js looks like:

    var webpack = require('webpack');
    
    var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
    var ExtractTextPlugin = require("extract-text-webpack-plugin");
    
    var entryPointsPathPrefix = './src/javascripts/pages';
    var WebpackConfig = {
      entry : {
        a: entryPointsPathPrefix + '/a.jsx',
        b: entryPointsPathPrefix + '/b.jsx',
        c: entryPointsPathPrefix + '/c.jsx',
        d: entryPointsPathPrefix + '/d.jsx'
      },
    
      // send to distribution
      output: {
        path: './dist/js',
        filename: '[name].js'
      },
    
      module: {
        loaders: [
          // Babel transformer (the one and only)
          { test: /\.js(x)?$/, exclude: /node_modules/, loader: "babel-loader"},
          { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")}
        ]
      },
    
      resolve: {
        // resolve file extensions
        extensions: ['.jsx', '.js', '']
      },
    
      plugins: [
        // separate common code
        new CommonsChunkPlugin('bundle.js'),
        new ExtractTextPlugin("[name].css")
      ]
    };
    
    module.exports = WebpackConfig;

    How do I specify file based outputs here?

  2. The example from @sokra wasnt exactly clear to me as well but it led me to a solution 👍

    It works like this @matthewmueller @activatedgeek

    We assume webpack.config.js is in the root directory of your project.

    entry: {
      'build/application/bundle': './src/application', // will be  ./build/application/bundle.js,
      'build/library/bundle': './src/library`'// will be  ./build/library/bundle.js
    },
    output: {
      path: './',
      filename: '[name].js'
    }
  3. @activatedgeek you sloved the problem? i think, i meet the same problem, but above answers don’t slove my problem.

    above answers, specify output files for “entry”, but i want sepcify output files for “output”

    for example:

    entry : {
      // this file require some files(like: big.png, big.css ...)
      // don't concat in one file, when output
      'index': './src/index.js'   
    },
    output: {
      path: './dist/'
    }
    
    //result of output path
    dist/
      |-- js/index.js
      |-- css/big.css
      |-- img/big.png
  4. This solution work for the entry chunks but I still have the problem when I use webpack.ensure method.

    • ./webpack.conf.js
        var entry = {
            './build/admin/admin.js': './src/admin/main.es6')
        };
    
        var output = {
            path: './',
            filename: '[name].js'
        };
    • ./src/admin/main.es6
            require.ensure(['./test'], (require) => {
                const test = require('./test')['default'];
                test.print();
            }, 'test')
    • ./src/view/test.html
        <script src="/assets/admin.js"></script>

    Admin chunk is perfectly build into the correct location but the test chunk will be created into ./ and I would like to have it into ./build/core/

    In this case I can modify my webpack.conf.js :

        var output = {
            path: './',
            filename: '[name].js',
            chunkFilename: './build/core/[name].js'
        };

    By doing that, the webpack.ensure will not be able to find the test chunk because the location is now

        /build/core/test.js
    

    and not

        /assets/test.js
    

    like I would like

    My question is :

    How can I change the location of my non-entry chunks without affect the filename ?

  5. +1 for multiple output files.

    Also want to ask @sokra what did he mean by his obscure answer. It is quite tactless of him to toss some info and disappear from conversation.

  6. @Luchillo You can add the extension to the entry keys. This worked for me:

    entry: {
      'js/app.js': './web/static/js/app.js',
      'js/vendor.js': ['react', 'react-dom'],
      'css/app.css': './web/static/css/app.scss',
    },
     output: {
      path: "./priv/static",
      filename: "[name]",
    },
    

    Don’t forget to update your plugins as well, e.g.

    plugins: [
      new webpack.optimize.CommonsChunkPlugin({name: 'js/vendor.js', children: true}),
      new ExtractTextPlugin('css/app.css', { allChunks: true }),
    ],
    
  7. @Luchillo I think I found a better solution. No need to specify an entry point for stylesheets when using the ExtractTextPlugin:

    entry: {
      'js/app': './web/static/js/app.js',
      'js/vendor': ['react', 'react-dom'],
    },
     output: {
      path: path.resolve(__dirname, 'priv/static'),
      filename: "[name]-[hash].js",
    },
    
    plugins: [
        new ExtractTextPlugin('css/styles-[contenthash].css', { allChunks: true }),
    ],
    

    You will need to require your root stylesheet, from which you @import all dependent stylesheets. E.g in web/static/js/index.js

    import '../css/style.scss';
    

    Better, e.g. because it allows you to add a hash to the generated the output files.

  8. What about having [chunkhash] for some entries and not for others? I think webpack cannot cover that use case.

  9. @tscislo how did you solve this promblem?

    new HtmlWebpackPlugin(
        {
            chunks: ['build/application/bundle'],
            template: './src/app/background/background.html',
            filename: ./'build/application/index.html',
            inject: 'body',
            hash: true
        })
    

    Then in index.html I got he following reference to my build.js

    <script src=”../build/application/bundle.build.js?fdeb2df34a550cb35144″></script>

    How to get rid of this ‘../build/application/’ part of the path since my index.html is in the same folder as my bundle?

  10. No plugins needed for multiple output paths 🙂

    const path = require('path');
    const PATHS = {
        root: path.join(__dirname, ''),
        app: path.join(__dirname, 'app') 
    };
    
    entry: {
           // ./build/index.js
            'build/index': [
                '${PATHS.app}/app.js',
                '${PATHS.app}/app_2.js'
            ],
    
            // ./library.js
            './library': [
                '${PATHS.library}/library.js',
            ]
        },   
        output: {
            path: PATHS.root,
            filename: '[name].js'
        }
  11. I finally figured out how to keep the entry names clean, while still controlling where they ended up. Utilizing some of the tricks here:

    entry: {
      public: './src/public/js/index.js',
      admin: './src/admin/js/index.js'
    },
    output: {
      filename: '[name]/[hash].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }

    Which resulted in:

    bilde

    With this setup, my HtmlWebpackPlugin, CommonsChunkPlugin & friends could reference the chunks with clean descriptive names.

  12. Hello, Team
    I need your help!!
    I need to create multi output folder structure similar to the following screenshoot:
    captura

    Into the “/dist/” I would like to create three folders with images folder, css folder, js folder.

    My webpack.config.js looks something like this:

    var path = require("path");
    const webpack = require('webpack');
    const ExtractTextPlugin = require("extract-text-webpack-plugin");
    const FileManagerPlugin = require('filemanager-webpack-plugin');
    
    const extractCSS = new ExtractTextPlugin("css/[name]-1.0.0.css");
    const extractSASS = new ExtractTextPlugin("es/[name].css");
    
    module.exports = function (env) {
    
      var isProd = false;
    
      if (env != null && env.production) {
        isProd = true;
      }
    
     var jsDev = "./js/[name]-bundle.js";
     var jsProd = "./js/[name]-" + date_string() + ".js";
     var configJs = isProd ? jsProd : jsDev; 
    
      return {
        context: path.resolve(__dirname, "src"),
        entry: {
          specials: './js/specials.js',
          checkout: './js/checkout.js',
          mobile: './js/mobile.js',
          screen: './js/screen.js',
          custom: './js/app.js'
        },
        output: {
          path: path.join(__dirname, "dist"),
          filename: configJs
        },
        module: {
          rules: [{
              test: /\.css$/,
              use: extractCSS.extract({
                fallback: "style-loader",
                use: "css-loader"
              })
            },
            {
              test: /\.scss$/,
              use: extractSASS.extract({
                fallback: "style-loader",
                use: ["css-loader", "sass-loader"]
              })
            },{
              test: /\.(jpg|svg|png|gif)$/,
              exclude: /fonts/,
              loaders: [{
                loader: 'file-loader',
                options: {
                  name: '[name].[ext]',
                  outputPath: './images/',
                  publicPath: ''
                }
              }]
            }, {
              test: /\.(eot|svg|ttf|woff|woff2)$/,
              exclude: /images/,
              loader: 'file-loader',
              options: {
                name: 'fonts/[name].[ext]',
                publicPath: ''
              }
            }
          ]
        },
        plugins: [
          extractSASS
        ]
      };

    The entry structure looks something like this:

    screen

    This “SRC” that should be create the output “dist” with three destination directories.

    Any help will be appreciated,

    Thank you,

  13. Is it really that hard and “out of the webpack design” or whatever, to just have a “output” config per entry, with one single global default value? WTF

  14. Hello,
    Finally, I have a solution!

    image

    Explanation:

    1. In the root create a folder with two files:
    • webpack.config.dev.js ===> For DEV vesion, that looks something like this:
    const
        webpack = require('webpack'),
        path = require('path'),
        MiniCssExtractPlugin = require("mini-css-extract-plugin"),
        VueLoaderPlugin = require('vue-loader/lib/plugin'),
        autoprefixer = require('autoprefixer'),
        OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    
    
    /* FUNCTIONS */
    const getWebpackParam = require('./func/getWebpackParam');
    
    /* INSTANCES */
    const CONFIG = {
        HASH: 'bundle',
        VERSION: 'develop',
        BUNDLE: getWebpackParam('bundle') || 'all',
        PATHS: {
            src: path.join(__dirname, '../src'),
            dist: path.join(__dirname, '../dist'),
        }
    };
    
    /* ENTRIES DEFINED */
    CONFIG.ENTRIES = {
        'specials':           [CONFIG.PATHS.src + '/js/specials.js'],
        'checkout':           [CONFIG.PATHS.src + '/js/checkout.js'],
        'checkout-mobile':    [CONFIG.PATHS.src + '/js/checkout-mobile.js'],
        'core-mobile':        [CONFIG.PATHS.src + '/js/core-mobile.js'],
        'stores':             [CONFIG.PATHS.src + '/js/stores.js'],
        'myaccount':          [CONFIG.PATHS.src + '/js/myaccount.js'],
        'plp':                [CONFIG.PATHS.src + '/js/plp.js'],
        'pdp':                [CONFIG.PATHS.src + '/js/pdp.js'],
        'search':             [CONFIG.PATHS.src + '/js/search.js'],
        'core-desktop':       [CONFIG.PATHS.src + '/js/core-desktop.js']
    };
    
    /* EXPORT CONFIGURATION */
    module.exports = {
        context: CONFIG.PATHS.src,
        mode: 'development',
        entry: () => {
            return (CONFIG.BUNDLE === 'all') ? CONFIG.ENTRIES : { [CONFIG.BUNDLE]: CONFIG.ENTRIES[CONFIG.BUNDLE]};
        },
        output: {
            path: CONFIG.PATHS.dist,
            filename: '[name]/js/[name]-' + CONFIG.HASH + '.js',
        },
        optimization: {
            minimize: true,
            minimizer: [
                new OptimizeCssAssetsPlugin({
                    cssProcessor: require('cssnano'),
                    cssProcessorOptions: {
                        discardComments: {
                            removeAll: true
                        },
                        zindex: false,
                    },
                    canPrint: true,
                })
            ],
            splitChunks: {
                cacheGroups: {
                    styles: {
                        name: 'styles',
                        test: /\.css$/,
                        chunks: 'all',
                        enforce: true
                    }
                }
            }
        },
        resolve: {
            extensions: ['.js', '.jsx', '.jsm'],
            alias: {
                styles: CONFIG.PATHS.src + '/sass'
            }
        },
        devtool: 'eval',
        module: {
            rules: [{
                    test: /\.(scss|css)$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        {
                            loader: "css-loader"
                        },
                        {
                            loader: "postcss-loader",
                            options: {
                                autoprefixer: {
                                    browsers: ["last 2 versions"]
                                },
                                plugins: () => [
                                    autoprefixer
                                ]
                            },
                        },
                        {
                            loader: "sass-loader",
                            options: {}
                        }
                    ]
                },
                {
                    test: /.jsx?$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                },
                {
                    test: /\.(jpg|svg|png|gif)$/,
                    exclude: /fonts/,
                    use: [{
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                            outputPath: './',
                            publicPath: ''
                        }
                    }]
                },
                {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: {
                        extractCSS: true,
                        loaders: {
                            js: 'babel-loader',
                            file: 'file-loader',
                            scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader',
                            sass: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax'
                        }
                    }
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: {
                        loader: "babel-loader"
                    }
                }
            ]
        },
        plugins: [
            new webpack.DefinePlugin({
                'process.env.HASH': JSON.stringify(CONFIG.HASH),
                'process.env.VERSION': JSON.stringify(CONFIG.VERSION)
            }),
            new MiniCssExtractPlugin({
                filename: "[name]/css/[name].css",
                chunkFilename: "[id].css"
            }),
            new VueLoaderPlugin()
        ],
        devServer: {
            contentBase: CONFIG.PATHS.dist,
            compress: true,
            headers: {
                'X-Content-Type-Options': 'nosniff',
                'X-Frame-Options': 'DENY'
            },
            open: true,
            overlay: {
                warnings: true,
                errors: true
            },
            port: 8080,
            publicPath: 'http://localhost:8080/',
            hot: true
        },
        stats: {
            children: false
        }
    };
    
    • webpack.config.prod.js ===> For LIVE vesion, that looks something like this:
    /* CORE & LIBRARIES */
    const
        webpack = require('webpack'),
        UglifyJsPlugin = require('uglifyjs-webpack-plugin'),
        FileManagerPlugin = require('filemanager-webpack-plugin'),
        MiniCssExtractPlugin = require('mini-css-extract-plugin'),
        VueLoaderPlugin = require('vue-loader/lib/plugin'),
        autoprefixer = require('autoprefixer'),
        OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'),
        path = require('path'),
        WebpackAutoInject = require('webpack-auto-inject-version')
        VersionFile = require('webpack-version-file-plugin');
    
    
    /* INSTANCES */
    const CONFIG = {
        HASH: Date.now(),
        VERSION: require(path.join(__dirname, '../') + '/package.json').version,
        FILENAME:{
            VERSION: require(path.join(__dirname, '../') + '/package.json').version.split('.').join('_')
        },
        PATHS: {
            core: path.join(__dirname, '../'),
            src: path.join(__dirname, '../src'),
            dist: path.join(__dirname, '../dist')
        }
    };
    
    /* ENTRIES DEFINED */
    CONFIG.ENTRIES = {
        'specials':           [CONFIG.PATHS.src + '/js/specials.js'],
        'checkout':           [CONFIG.PATHS.src + '/js/checkout.js'],
        'checkout-mobile':    [CONFIG.PATHS.src + '/js/checkout-mobile.js'],
        'core-mobile':        [CONFIG.PATHS.src + '/js/core-mobile.js'],
        'stores':             [CONFIG.PATHS.src + '/js/stores.js'],
        'myaccount':          [CONFIG.PATHS.src + '/js/myaccount.js'],
        'plp':                [CONFIG.PATHS.src + '/js/plp.js'],
        'pdp':                [CONFIG.PATHS.src + '/js/pdp.js'],
        'search':             [CONFIG.PATHS.src + '/js/search.js'],
        'core-desktop':       [CONFIG.PATHS.src + '/js/core-desktop.js']
    };
    
    /* EXPORT CONFIGURATION */
    module.exports = {
        context: CONFIG.PATHS.src,
        mode: 'production',
        entry: CONFIG.ENTRIES,
        output: {
            path: CONFIG.PATHS.dist,
            filename: (chunkData) => {
                if(chunkData.chunk.name === 'specials') return '[name]/js/[name]-bundle.js'
                return '[name]/js/[name]-' + CONFIG.VERSION + '.js';
            },
            publicPath: './'
        },
        optimization: {
            minimize: true,
            minimizer: [
                new UglifyJsPlugin({
                    cache: true,
                    parallel: true,
                    sourceMap: true
                }),
                new OptimizeCssAssetsPlugin({
                    cssProcessor: require('cssnano'),
                    cssProcessorOptions: {
                        discardComments: {
                            removeAll: true
                        },
                        zindex: false,
                    },
                    canPrint: true,
                })
            ],
            splitChunks: {
                cacheGroups: {
                    styles: {
                        name: 'styles',
                        test: /\.css$/,
                        chunks: 'all',
                        enforce: true
                    }
                }
            },
        },
        resolve: {
            extensions: ['.js', '.jsx', '.jsm'],
            alias: {
                styles: CONFIG.PATHS.src + '/sass'
            }
        },
        module: {
            rules: [{
                    test: /\.(scss|css)$/,
                    use: [
                        MiniCssExtractPlugin.loader,
                        {
                            loader: "css-loader"
                        },
                        {
                            loader: "postcss-loader",
                            options: {
                                autoprefixer: {
                                    browsers: ["last 2 versions"]
                                },
                                plugins: () => [
                                    autoprefixer
                                ]
                            },
                        },
                        {
                            loader: "sass-loader",
                            options: {}
                        }
                    ]
                },
                {
                    test: /\.m?js$/,
                    exclude: /(node_modules|bower_components)/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env']
                        }
                    }
                },
                {
                    test: /.jsx?$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader'
                },
                {
                    test: /\.(jpg|svg|png|gif)$/,
                    exclude: /fonts/,
                    use: [{
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                            outputPath: './',
                            publicPath: ''
                        }
                    }]
                },
                {
                    test: /\.vue$/,
                    loader: 'vue-loader',
                    options: {
                        extractCSS: true,
                        loaders: {
                            js: 'babel-loader',
                            file: 'file-loader',
                            scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader',
                            sass: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax'
                        }
                    }
                }
            ]
        },
        plugins: [
            new VersionFile({
                packageFile:path.join(CONFIG.PATHS.core, 'package.json'),
                template: path.join(CONFIG.PATHS.core, 'version.ejs'),
                outputFile: path.join(CONFIG.PATHS.dist, 'version.json')
            }),
            new WebpackAutoInject({
                components: {
                    InjectAsComment: true,
                    AutoIncreaseVersion: false
                },
                componentsOptions:{
                    InjectAsComment: {
                        tag: `
    Build version: {version} - {date}
    `,
                        dateFormat: 'dddd, mmmm dS, yyyy, h:MM:ss TT',
                        multiLineCommentType: true,
                      },
                }
            }),
            new webpack.ProvidePlugin({
                Promise: 'es6-promise-promise'
            }),
            new webpack.DefinePlugin({
                'process.env.HASH': CONFIG.HASH,
                'process.env.VERSION': JSON.stringify(CONFIG.VERSION)
            }),
            new MiniCssExtractPlugin({
                filename: '[name]/css/[name]-'+ CONFIG.VERSION +'.css',
                chunkFilename: '[id].css'
            }),
            new VueLoaderPlugin(),
            new FileManagerPlugin({
                onEnd: {
                    copy: [{
                            source: CONFIG.PATHS.src + '/images',
                            destination: CONFIG.PATHS.dist + '/skin/es/images',
                        },
                        {
                            source: CONFIG.PATHS.src + '/images',
                            destination: CONFIG.PATHS.dist + '/mob/es/images',
                        },
                        {
                            source: CONFIG.PATHS.dist + '/core-desktop/css/core-desktop-'+ CONFIG.VERSION +'.css',
                            destination: CONFIG.PATHS.dist + '/skin/es/screen.css',
                        },
                        {
                            source: CONFIG.PATHS.dist + '/checkout/css/checkout-'+ CONFIG.VERSION +'.css',
                            destination: CONFIG.PATHS.dist + '/skin/es/checkout.css',
                        },
                        {
                            source: CONFIG.PATHS.dist + '/core-mobile/css/core-mobile-'+ CONFIG.VERSION +'.css',
                            destination: CONFIG.PATHS.dist + '/mob/es/screen.css'
                        }
                    ],
                    archive: [{
                            source: CONFIG.PATHS.dist + '/skin',
                            destination: CONFIG.PATHS.dist + '/skin_' + CONFIG.FILENAME.VERSION + '.zip'
                        },
                        {
                            source: CONFIG.PATHS.dist + '/mob',
                            destination: CONFIG.PATHS.dist + '/mob_' + CONFIG.FILENAME.VERSION + '.zip'
                        }
                    ],
                    delete: [
                        CONFIG.PATHS.dist + '/mob',
                        CONFIG.PATHS.dist + '/skin',
                        CONFIG.PATHS.dist + '/images',
                    ]
                }
    
            })
        ]
    };
    
    1. In the root. create a file for define the naming and file version, looks like this:
    {
    	"version" : {
    		"name":      "<%%= package.name %%>",
    		"buildDate": "<%%= currentTime %%>",
    		"version":   "<%%= package.version %%>"
    	}
    }
    
    1. Finally in the root, create the main config JS. that looks something like this:
    const TARGET = process.env.npm_lifecycle_event;
    
    if (TARGET.indexOf('dev') !== -1 || !TARGET) {
        module.exports = require('./config/webpack.config.dev');
        console.info('DEVELOP MODE - CONFIGURATION FROM --> ./config/webpack.config.dev.js');
    }
    if (TARGET === 'build' || TARGET === 'stats') {
        module.exports = require('./config/webpack.config.prod');
        console.info('PRODUCTION MODE - CONFIGURATION FROM --> ./config/webpack.config.prod.js');
    }
    

    This setup workin properly (Keep in mind installing the necessary modules defined in every file)

    The output:

    image

Comments are closed.