This article mainly talks about the optimization of React and Webpack. The original project is here and will continue to be updated whenever possible. Please pay attention and start

Build optimization

loaders

  • Use different loaders/plugins as little as possible
  • useincludeField specifies the directory to convert, usingexcludeExcluded directories:
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'),
        exclude: path.resolve(__dirname, 'node_modules'),
        loader: 'babel-loader'}}};Copy the code

resolve

  • Minimize the number of resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles values

  • resolve.modules:

    Use resolve. Modules to specify the path to the module directory:

    module.exports = {
        ...
        resolve: {
          modules: [path.resolve(__dirname, 'node_modules')]}};Copy the code
  • resolve.alias:

    Resolve. Alias enables Webpack to use a compressed version of the library directly, without parsing the library, and to use aliases to easily reference files:

    module.exports = {
        ...
        resolve: {
          alias: {
            Components: path.resolve(__dirname, 'src/components/'),
            Utils: path.resolve(__dirname, 'src/utils/'),
            react: patch.resolve(__dirname, './node_modules/react/dist/react.min.js')}}};Copy the code

    This way, for example, you can use the compressed version of React directly without having to parse it again each time you build. You can also refer to a file through an alias instead of typing a long reference path:

    import ReactComponent from 'Components/ReactComponent';
    Copy the code

    The downside is that you won’t be able to use tree-shaking, so it’s better to use a more holistic library like React, while libraries like LoDash use tree-shaking to remove unnecessary code.

  • resolve.extensions:

    Set the file suffix to be resolved. The default value is:

    module.exports = {
        ...
        resolve: {
          extensions: ['.wasm'.'.mjs'.'.js'.'.json']}};Copy the code

    Can be set to their own file type to parse, speed up the search speed:

    module.exports = {
        ...
        resolve: {
          extensions: ['.js'.'.json'.'jsx']}};Copy the code

externals

Using externals prevents some libraries from being packaged and references the library in other ways (such as a CDN). This has the advantage of not affecting the caching of the library code when the code is updated and the user can simply download the new code. Of course, we can also use chunk to package infrequently updated libraries in another file, which we’ll talk about next.

For example, introduce React from the CDN:

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js" defer></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" defer></script>
<script src="./dist/index.js" defer></script>
Copy the code
module.exports = {
    ...
    externals: {
      react: 'React'.'react-dom': 'ReactDOM'}},Copy the code

devtool

Devtool is very performance intensive, so don’t set it if you don’t need it. If you need it and the quality is good, you can set it to source-map, but this is time-consuming. If you can accept poor quality, you can use cheap source-map. The official recommendation is to use cheap-module-eval-source-map with better performance and lower quality.

splitChunks

After Webpack 4, change the common code extraction tool from CommonChunksPlugin to the better SplitChunksPlugin. The following example does not use externals, but extracts React and ReactDOM into public module code.

module.exports = {
  ...
  // externals: {
  // react: 'React',
  // 'react-dom': 'ReactDOM'
  // },
  optimization: {
    ...
    splitChunks: {
      chunks: 'all'.name: true.automaticNameDelimiter: The '-'.// Connecter between modules, default is "~"
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/.priority: - 10  // Priority. The smaller the priority, the higher the priority
        },
        default: {  // The default setting can be overridden
          minChunks: 2.priority: - 20.reuseExistingChunk: true  // If the code has already been extracted, reuse it instead of reproducing it}}}},}Copy the code

mode

Mode can be:

  • production: Build mode, which automatically enables build-related plug-ins such as compressed code.
module.exports = {
+  mode: 'production',
-  plugins: [
-    new UglifyJsPlugin(/* ... */),
-    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-    new webpack.optimize.ModuleConcatenationPlugin(),
-    new webpack.NoEmitOnErrorsPlugin()
-  ]
}
Copy the code
  • development: Development mode, which launches some development-related optimization plug-ins.
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.NamedChunksPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development")}), -]}Copy the code
  • node

Babel, Tree – Shaking

The version used here is Babel 7. Since most browsers already support ES6 syntax, converting all of your code to ES5 would probably result in a lot of extra code, so here’s just a partial conversion. What about compatibility with older browsers?

{
    "presets": [["@babel/react",
            {
                "modules": false  // 关闭babel的模块转换,才能使用Webpack的Tree-Shaking功能
            }
        ]
    ],
    "plugins": [
        "@babel/plugin-proposal-class-properties"// class, this should be in front, otherwise it may be an error"@babel/plugin-transform-classes",  // class
        "@babel/plugin-transform-arrow-functions"// Arrow function"@babel/plugin-transform-template-literals"// String template]}Copy the code

Tree-shaking is well supported when the sideEffects of some libraries such as Lodash are set for package.json:

{
  "name": "lodash"."sideEffects": false
}
Copy the code

happypack

Use happypack to enable multithreading to speed up processing of loader:

var HappyPack = require('happypack');

module.exports = {
    ...
    rules: [
      {
        test: /\.(js|jsx)$/.include: path.resolve(__dirname, 'src'),
        exclude: path.resolve(__dirname, 'node_modules'),
        use: 'happypack/loader? id=babel'},].plugins: [
      new HappyPack({
        id: 'babel'.loaders: ['babel-loader? cacheDirectory']}),],}Copy the code

other

Build the code into ES6+

As mentioned above, converting code to ES5 can be time-consuming and potentially redundant, since most browsers now support ES6 syntax, let’s take a look at compatibility with older browsers.

  1. module,nomodule:

You can use to load ES6+ code because browsers that support this property must support async/await, Promise, class, etc. Browsers that do not support it will choose to ignore it and not load it.

So you also need an ES5 script that is compatible with older browsers. Use to load the ES5 code. Browsers that recognize nomodule will ignore it. Older browsers that don’t recognize it will load it. This makes it compatible with older browsers while newer browsers use much less ES6+ code.

However, there is a disadvantage to this approach: when splitChunks are used to break the code into larger chunks, a large number of two versions of the code are generated.

  1. Dynamic polyfill
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
Copy the code

It does the polyfills required to automatically load the browser by parsing the UserAgent in the request header information. If you access the above connection using a newer version you will find that there is not much code, whereas with IE there is a lot. This allows us to use ES6+ code and dynamic Polyfill to be compatible with older browsers, but dynamic Polyfill doesn’t support classes, arrow functions, etc., so Babel needs to be configured as above to convert these to ES5. To learn more about dynamic polyfill, click here.

The development of optimization

Avoid using tools you didn’t use until build time

For example, UglifyJsPlugin does not need to compress the code during development. The following tools are also avoided during development:

  • UglifyJsPlugin
  • ExtractTextPlugin
  • [hash]/[chunkhash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

Do not output path information

module.exports = {
  // ...
  output: {
    pathinfo: false}};Copy the code

Turn off partial build optimizations

module.exports = {
  ...
  optimization: {
    removeAvailableModules: false.removeEmptyChunks: false.splitChunks: false,}};Copy the code

The React to optimize

Because the React of HTML elements are written in the book of JS file, so generally leads to build JS file is very large, and in the process of loading and execution of JS long, the user’s browser has been display is white, the first screen rendering time become very long, do not use the service side can render some improvement according to the following method.

Adding a first-screen loading

The HTML file can be loaded by using the HtmlWebpackPlugin to avoid a blank screen.

var loading = {
  ejs: fs.readFileSync(path.resolve(__dirname, 'template/loading.ejs')),
  css: fs.readFileSync(path.resolve(__dirname, 'template/loading.css')),}; module.exports = { ... plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname,'template/index.ejs'),
          hash: true, loading, loading, / / in the React before rendering the added loading}), new ScriptExtHtmlWebpackPlugin ({/ / add defer to script tags defaultAttribute:'defer']}}),Copy the code

See here for the template code

prerender-spa-plugin

Prerender-spa-plugin can generate the first screen of a single-page app into HTML by accessing the appropriate path through the Puppeteer, which I won’t go into because I’ve never been able to install the puppeteer.

module.exports = {
    ...
    new PrerenderSpaPlugin(
      // Absolute path to compiled SPA
      path.resolve(__dirname, '.. /dist'),
      // List of routes to prerender
      ['/'])}Copy the code

React Loadable

It can be used to dynamically import React components, and it can be used to dynamically import minor components into chunks, which can improve the rendering speed of the first screen:

import Loading from './src/components/Loading';
import ReactDOM from 'react-dom';
import Loadable from 'react-loadable';

const LoadableApp = Loadable({
  loader: (a)= > import('./src/App'),
  loading: Loading,
});

ReactDOM.render(LoadableApp, document.querySelector('#root'));

Copy the code

For now, I will write so many optimized places, and I will continue to update when I have time. Welcome to discuss any questions ~