question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Incremental build performance issues

See original GitHub issue

I’m experiencing some incremental build performance issues. These are taken by sampling the Time output from webpack incremental builds.

RRWP+babel+ts-loader+ForkTsCheckerWebpackPlugins: 11-17 s RRWP+babel+ts: 9-16 s babel+ts-loader+ForkTsCheckerWebpackPlugin: 1.8-2.0s babel+ts-loader: 1.5s-1.7s babel: 1.4-2.1 s ts-loader+ForkTsCheckerWebpackPlugin: 1.1-1.9 s

It looks like RRWP is adding 8-15 seconds build time to my typescript react project.

The config is as follows: webpack.common.js

const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  target: 'web',

  entry: {
    core: [
      path.resolve(__dirname, 'ts/core/polyfill.ts'),
      path.resolve(__dirname, 'ts/core/core.ts'),
    ],
    reports: [
      path.resolve(__dirname, 'ts/core/polyfill.ts'),
      path.resolve(__dirname, 'ts/core/reports.ts'),
    ],
    app: [path.resolve(__dirname, 'ts/apps/index.tsx')],
  },

  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json', '.html'],
    alias: {
      Shared: path.resolve(__dirname, 'ts/shared'),
      appA: path.resolve(__dirname, 'ts/apps/appA'),
      appB: path.resolve(__dirname, 'ts/apps/appB'),
    },
  },

  module: {
    rules: [
      // All image files will be handled here
      {
        test: /\.(png|svg|jpg|gif)$/,
        loader: 'file-loader',
      },

      // All font files will be handled here
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        loader: 'file-loader',
      },

      // All files with ".html" will be handled
      { test: /\.html$/, loader: 'html-loader' },

      // All output ".js" files will have any sourcemaps re-processed by "source-map-loader".
      { enforce: 'pre', test: /\.js$/, exclude: [/node_modules/], loader: 'source-map-loader' },
    ],
  },

  plugins: [
    // Clean dist folder.
    new CleanWebpackPlugin({ verbose: true }),

    // Split out library into seperate bundle and remove from app bundle.
    new webpack.HashedModuleIdsPlugin(),

    // avoid publishing when compilation failed.
    new webpack.NoEmitOnErrorsPlugin(),

    new HtmlWebpackPlugin({
      inject: false,
      title: null,
      chunks: ['common', 'app', 'core'],
      heads: ['common', 'core'],
      bodys: ['app'],
      filename: '../Views/Shared/_Layout.cshtml',
      template: './Views/Shared/_Layout_Template.cshtml',
      appMountId: 'react-app',
    }),
    new HtmlWebpackPlugin({
      inject: false,
      title: null,
      chunks: ['common', 'core'],
      heads: ['common', 'core'],
      bodys: [],
      filename: '../Views/Shared/_ExternalLayout.cshtml',
      template: './Views/Shared/_ExternalLayout_Template.cshtml',
    }),
    new HtmlWebpackPlugin({
      inject: false,
      title: null,
      chunks: ['common', 'reports'],
      heads: ['common', 'reports'],
      bodys: [],
      filename: '../Views/Shared/_ReportLayout.cshtml',
      template: './Views/Shared/_reportLayout_Template.cshtml',
    }),

    new CopyWebpackPlugin([
      'ts/core/dependencies/telerik-report-viewer/telerikReportViewerTemplate-sass.html',
    ]),

    // Moment.js is an extremely popular library that bundles large locale files
    // by default due to how Webpack interprets its code. This is a practical
    // solution that requires the user to opt into importing specific locales.
    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
    // You can remove this if you don't use Moment.js:
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    new ForkTsCheckerWebpackPlugin(),
  ],

  // Some libraries import Node modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    dgram: 'empty',
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
    child_process: 'empty',
  },
  // Turn off performance hints during development because we don't do any
  // splitting or minification in interest of speed. These warnings become
  // cumbersome.
  performance: {
    hints: false,
  },

  // pretty terminal output
  stats: { colors: true },

  // Set up chunks
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        default: false,
        vendors: false,
        commons: {
          name: 'common',
          chunks: 'initial',
          minChunks: 2,
        },
      },
    },
  },
};

and webpack.dev.js

const webpack = require('webpack');
const Merge = require('webpack-merge');
const CommonConfig = require('./webpack.common.js');
const postcssConfig = require('./postcss-config');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

const babelLoader = {
  loader: 'babel-loader?cacheDirectory', //require.resolve('babel-loader'),
  options: {
    presets: ['@babel/react', '@babel/typescript', ['@babel/env', { modules: false }]],
    plugins: [
      '@babel/plugin-proposal-class-properties',
      '@babel/plugin-proposal-optional-chaining',
      '@babel/plugin-proposal-nullish-coalescing-operator',
      'react-refresh/babel',
    ],
  },
};

module.exports = Merge(CommonConfig, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': 'http://localhost:2354',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    },
    writeToDisk: true,
    hot: true,
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist',
    publicPath: 'http://localhost:8080/dist/',
    pathinfo: false,
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          babelLoader,
          {
            loader: 'ts-loader',
            options: {
              transpileOnly: true,
              experimentalWatchApi: true,
            },
          },
        ],
      },

      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: [babelLoader],
      },
      {
        // All css files will be handled here
        oneOf: [
          {
            test: /^((?!\.module).)*scss$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  sourceMap: true,
                },
              },
              postcssConfig,
              {
                loader: 'sass-loader',
                options: {
                  implementation: require('node-sass'),
                  sourceMap: true,
                },
              },
            ],
          },
          {
            test: /\.module.scss$/,
            use: [
              'style-loader',
              {
                loader: 'typings-for-css-modules-loader',
                options: {
                  namedExport: true,
                  camelCase: true,
                  importLoaders: 2,
                  localIdentName: '[path][name]__[local]--[hash:base64:5]',
                  modules: true,
                  sourceMap: true,
                },
              },
              postcssConfig,
              {
                loader: 'sass-loader',
                options: {
                  implementation: require('node-sass'),
                  sourceMap: true,
                },
              },
            ],
          },

          // All files with ".less" will be handled and transpiled to css
          {
            test: /^((?!\.module).)*less$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 2,
                  sourceMap: true,
                },
              },
              postcssConfig,
              {
                loader: 'less-loader',
                options: {
                  sourceMap: true,
                },
              },
            ],
          },
          {
            test: /\.module.less$/,
            use: [
              'style-loader',
              {
                loader: 'typings-for-css-modules-loader',
                options: {
                  namedExport: true,
                  camelCase: true,
                  importLoaders: 2,
                  localIdentName: '[path][name]__[local]--[hash:base64:5]',
                  modules: true,
                  sourceMap: true,
                },
              },
              postcssConfig,
              {
                loader: 'less-loader',
                options: {
                  sourceMap: true,
                },
              },
            ],
          },
          {
            test: /^((?!\.module).)*css$/,
            use: [
              'style-loader',
              {
                loader: 'css-loader',
                options: {
                  importLoaders: 1,
                  sourceMap: true,
                },
              },
              postcssConfig,
            ],
          },
          {
            test: /module.css$/,
            use: [
              'style-loader',
              {
                loader: 'typings-for-css-modules-loader',
                options: {
                  namedExport: true,
                  camelCase: true,
                  importLoaders: 2,
                  localIdentName: '[path][name]__[local]--[hash:base64:5]',
                  modules: true,
                  sourceMap: true,
                },
              },
              postcssConfig,
            ],
          },
        ],
      },
    ],
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('development'),
      },
      PRODUCTION: false,
    }),
    new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }),
  ],
});

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
pmmmwhcommented, Dec 13, 2019

I don’t have time to look at this until Sunday night, but from what I read from the Webpack docs, removing addDependency is safe, because the file is static. I am guessing that addDependency broke Webpack’s default caching behaviour, so thus why adding cacheable could fix that.

Moving the require outside of the loader should further ensure better caching since Node will cache it.

1reaction
mmhand123commented, Dec 12, 2019

Yeah definitely. I actually ended up moving the require outside of the loader along with removing the dependency. I’ll make a pr too for @pmmmwh to look at whenever there’s free time!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Improving the speed of incremental builds - Apple Developer
When a target has many dependencies, or when it depends on large, monolithic modules, Xcode must serialize more tasks. To improve build performance,...
Read more >
Angular incremental build performance problems for existing ...
Current Behavior It seems to be the case that when enabling incremental build for Angular apps (following this guide ...
Read more >
Build Performance - webpack
This guide contains some useful tips for improving build/compilation performance. General. The following best practices should help, whether you're running ...
Read more >
Improve Visual Studio Build Performance - NDepend
The procedure to know if your solution has a problem with incremental build is easy: first use the command Build > Rebuild Solution...
Read more >
Incremental Builds - Nx
In an incremental build scenario, when building the app, all it's dependencies need to be built first. In our scenario above, that means...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found