SPFx overclockers or how to significantly speed up the "gulp serve" command

A few months ago I wrote an article about SharePoint Framework build performance - SPFx overclockers or how to significantly improve your SharePoint Framework build performance. I've tried to reduce the amount of time the "gulp serve" command uses to re-build your code and to finally refresh a browser. I used different optimization technics for that purpose. The idea was to tweak the default SPFx build pipeline. However, those post only partially solves the problem. 

In this post, I will solve the problem from another way around (spoiler: I managed to make "serve" 10-15 times faster). 

The idea

How SharePoint Framework's "gulp serve" works? It gets your sources and outputs javascript bundles. But "gulp serve" becomes slow when your solution grows. How to fix that issue? Well, since it's slow, then don't use it! 

So this is the idea - don't use "gulp serve", use a completely custom webpack based build to transform your sources into exactly the same javascript bundles which are produced by "gulp serve". 

What is the input for "gulp serve"? - TypeScript sources, styles. What is the output? - Javascript files. Can we get sources and produce exactly the same javascript? -Yes.

How it works

Some nerdy content goes below. If you don't want to read about internal implementation, go directly to "How can I use it?". 

Also please note, that the internal implementation was changed since this post was written, however the main idea is still the same.

To make it work, we need a custom webpack config and webpack dev server to serve webpack output.

webpack config

The most essential part of the webpack config is below (it's not exact config used, but just to give you the idea):

module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          compilerOptions: {
            declarationMap: false
          }
        },
        exclude: /node_modules/
      },
      {
        use: [{
          loader: "@microsoft/loader-cased-file",
          options: {
            name: "[name:lower]_[hash].[ext]"
          }
        }],
        test: /.(jpg|png|woff|eot|ttf|svg|gif|dds)((\\?|\\#).+)?$/
      },
      {
        use: [{
          loader: "html-loader"
        }],
        test: /\.html$/
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: "@microsoft/loader-load-themed-styles",
            options: {
              async: true
            }
          },
          {
            loader: 'css-loader'
          }
        ]
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: "@microsoft/loader-load-themed-styles",
            options: {
              async: true
            }
          },
          'css-modules-typescript-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }, // translates CSS into CommonJS
          "sass-loader" // compiles Sass to CSS, using Node Sass by default
        ]
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      tslint: true
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.DEBUG': JSON.stringify(true),
      'DEBUG': JSON.stringify(true)
    })],

The key things here are loaders:

  • ts-loader transforms TypeScript to Javascript. It doesn't perform type checking (transpileOnly=true), thus works very fast. More on that below in plugins section
  • @microsoft/loader-cased-file - that's the same loader used by SharePoint Framework webpack task, it loads fonts, images
  • html-loader - loads HTML files. It's included into the default SharePoint Framework webpack task, so I added it as well
  • @microsoft/loader-load-themed-styles - that's also OOB loader, it transforms themable css into real css colors, thus very important
  • sass-loader - loads .scss files and transforms to .css
  • css-modules-typescript-loader - this loader transforms imports from *.module.scss files into TypeScript type definitions. 

Also, I use ForkTsCheckerWebpackPlugin. It performs type checks and linting in a separate asynchronous thread, thus it doesn't affect build performance. 

But it's not all. We also need "externals", "output" and "entry" values for our webpack config. Basically, these values are based on your SharePoint Framework solution configuration. And here is the trick - I have a pre-build task, which writes intermediate SPFx webpack config to the disk. Then when custom webpack build starts, it reads that config and sets correct "externals", "output" and "entry" values. 

webpack dev server

 We also need to serve our bundles. Webpack dev server is a perfect solution for that task:

devServer: {
    hot: false,
    contentBase: resolve(__dirname),
    publicPath: host + "/dist/",
    host: "localhost",
    port: 4321,
    disableHostCheck: true,
    historyApiFallback: true,
    open: true,
    openPage: host + "/temp/workbench.html",
    stats: {
      colors: true,
      chunks: false,
      "errors-only": true
    },
    proxy: { // url re-write for resources to be served directly from src folder
      "/lib/**/loc/*.js": {
        target: host,
        pathRewrite: { '^/lib': '/src' },
        secure: false
      }
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    https: {
      cert: CertificateStore.instance.certificateData,
      key: CertificateStore.instance.keyData
    }
  },

Some key things here:

  • publicPath should be "https://localhost:4321/dist", since that's the location used by OOB "gulp serve"
  • I use proxy to re-map requests for localization resources from lib folder to src
  • for https configuration, I use OOB certificates (@microsoft/gulp-core-build-serve/lib/CertificateStore)

How can I use it?

For "How can I use" I recommend following the guide on GitHub, since it's the most recent and this post might not be in sync with the latest version.