Search code examples
webpackbuildbundlewebpack-4

Webpack 4.0: Production Config Doesn't Create Bundle Chunk Files


I am trying to optimize the build and bundle size of my project. However, whenever I run npm run build it does not create any bundle chunk files under my /dist folder. However npm run dev creates a the bundle chunks under my /dist folder just fine. The only difference between the two npm scripts is that build uses my webpack.prod configurations and dev uses my webpack.dev configuration.

The output of npm run build in my terminal seems to indicate that it's creating all the chunks that I specified (I create one chunk for the main bundle and one chunk for each node module to minimize the bundles a user needs to download whenever we update our project).

I'm not sure if there's some default behavior in Webpack's production mode that's causing this.

npm run build Output:

Webpack.prod output

Here are my two Webpack configuration files:

Webpack.prod:

module.exports = env => {
  // Get the root path (assuming your webpack config is in the root of your project!)
  const currentPath = path.join(__dirname);

  // Create the fallback path (the production .env)
  const basePath = currentPath + '/.env';

  // We're concatenating the environment name to our filename to specify the correct env file!
  const envPath = basePath + '.' + env.ENVIRONMENT;

  // Check if the file exists, otherwise fall back to the production .env
  const finalPath = fs.existsSync(envPath) ? envPath : basePath;

  return {
    mode: 'production',
    entry: ['babel-polyfill', 'react', 'react-dom', './src/Index.tsx'],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist/')
    },
    resolve: {
      extensions: [".ts", ".tsx", ".js", ".json"]
    },
    watch: false,
    devServer: {
      contentBase: 'dist',
      port: 3000,
      historyApiFallback: true,
      inline: true,
      https: true
    },
    optimization: {
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity,
        minSize: 0,
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
              // get the name. E.g. node_modules/packageName/not/this/part.js
              // or node_modules/packageName
              const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

              // npm package names are URL-safe, but some servers don't like @ symbols
              return `npm.${packageName.replace('@', '')}`;
            },
          },
        },
      }
    },
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          loader: "ts-loader",
          exclude: [
            /node_modules/,
          ],
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
          exclude: [
            /node_modules/,
          ],
        },
        {
          test: /\.(png|gif|jpg|woff|eot|ttf|svg|woff2|ico)$/i,
          use: "file-loader?name=images/[name].[ext]",
        },
        {
          test: /\.scss$/,
          use: ['style-loader', 'css-loader', 'sass-loader']
        },
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader']
        },
        {
          test: /\.(config)$/,
          use: "file-loader?name=[name].[ext]"
        },
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "index.html",
        filename: "index.html",
      }),
      new TSLintPlugin({
        files: ['./src/**/*.ts']
      }),
      new CopyWebpackPlugin([
        { from: './src/favicon.ico' },
        { from: './data/*.json', to: path.resolve(__dirname, 'dist/'), force: true },
      ], {}),
      new Dotenv({
        path: finalPath
      })
    ]
  }
};

Webpack.dev:

module.exports = env => {
  // Get the root path (assuming your webpack config is in the root of your project!)
  const currentPath = path.join(__dirname);

  // Create the fallback path (the production .env)
  const basePath = currentPath + '/.env';

  // We're concatenating the environment name to our filename to specify the correct env file!
  const envPath = basePath + '.' + env.ENVIRONMENT;

  // Check if the file exists, otherwise fall back to the production .env
  const finalPath = fs.existsSync(envPath) ? envPath : basePath;

  return {
    mode: 'development',
    devtool: "inline-source-map",
    entry: ['babel-polyfill', 'react', 'react-dom', './src/Index.tsx'],
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist/')
    },
    resolve: {
      // Add '.ts' and '.tsx' as resolvable extensions.
      extensions: [".ts", ".tsx", ".js", ".json"]
    },
    watch: false,
    // Enable sourcemaps for debugging webpack's output.
    devServer: {
      contentBase: 'dist',
      historyApiFallback: true,
      inline: true,
      port: 3000,
      https: true
    },
    optimization: {
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity,
        minSize: 0,
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
              // get the name. E.g. node_modules/packageName/not/this/part.js
              // or node_modules/packageName
              const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

              // npm package names are URL-safe, but some servers don't like @ symbols
              return `npm.${packageName.replace('@', '')}`;
            },
          },
        },
      }
    },
    module: {
      rules: [
        // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
        {
          test: /\.tsx?$/,
          loader: "ts-loader"
        },
        {
          test: /\.(png|gif|jpg|woff|eot|ttf|svg|woff2|ico)$/i,
          use: "file-loader?name=images/[name].[ext]",
        },
        {
          test: /\.scss$/,
          use: ['style-loader', 'css-loader', 'sass-loader']
        },
        {
          test: /\.css$/,
          use: ['style-loader', 'css-loader']
        },
        // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
        {
          enforce: "pre",
          test: /\.js$/,
          loader: "source-map-loader",
          exclude: [/node_modules/, /build/, /__test__/],
        },
        {
          test: /\.(config)$/,
          use: "file-loader?name=[name].[ext]"
        },
        {
          test: /\.js$/,
          loader: 'babel-loader'
        }
      ]
    },

    // When importing a module whose path matches one of the following, just
    // assume a corresponding global variable exists and use that instead.
    // This is important because it allows us to avoid bundling all of our
    // dependencies, which allows browsers to cache those libraries between builds.
    externals: {
      // "react": "React",
      // "react-dom": "ReactDOM"
    },

    performance: { hints: false },

    plugins: [
      new HtmlWebpackPlugin({
        template: "index.html",
        filename: "index.html",
      }),
      new TSLintPlugin({
        files: ['./src/**/*.ts']
      }),
      new CopyWebpackPlugin([
        { from: './src/favicon.ico' },
        { from: './data/*.json', to: path.resolve(__dirname, 'dist/'), force: true  } ,
      ], {}),
      new Dotenv({
        path: finalPath
      })
    ]
  }
};

Solution

  • I figured it out. By default, Webpack's production mode sets the property onEmitOnErrors under optimization to true. I'm using TypeScript and because I had an unresolved type error (it did not break the application so it wasn't critical), the build wasn't emitted by Webpack.

    I hope this helps!