Search code examples
javascriptwebpackpolyfillsbabel-polyfill

how to make webpack import polyfills only for a specific build?


I just managed to make webpack create two separete builds one for es5 and another for es6. See below the config file:

const path = require("path");

const common = require("./webpack.common");
const merge = require("webpack-merge");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
var HtmlWebpackPlugin = require("html-webpack-plugin");

const es5Config = merge(common,{
  mode: "production",
  output: {
    filename: "[name].[contentHash].bundle.es5.js",
    path: path.resolve(__dirname, "dist")
  },
  optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin(),
      new TerserPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/template.html",
        minify: {
          removeAttributeQuotes: true,
          collapseWhitespace: true,
          removeComments: true
        }
      }),
    ]
  },
  plugins: [

    new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
      new CleanWebpackPlugin(),
    ],

  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader, //3. Extract css into files
          "css-loader", //2. Turns css into commonjs
          "sass-loader" //1. Turns sass into css
        ]
      },
      {
      test: /\.m?js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env', {
              modules: false,
              useBuiltIns: 'entry',
              targets: {
                browsers: [
                  "IE 11"
                ],
              },
            }],
          ],
        },
      },
    }],
  },
});

const es6Config = merge(common, {
  mode: "production",
  output: {
    filename: "[name].[contentHash].bundle.es6.js",
    path: path.resolve(__dirname, "dist")
  },
  optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin(),
      new TerserPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/template.html",
        minify: {
          removeAttributeQuotes: true,
          collapseWhitespace: true,
          removeComments: true
        }
      }),
    ]
  },
  plugins: [

    new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
      new CleanWebpackPlugin(),
    ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader, //3. Extract css into files
          "css-loader", //2. Turns css into commonjs
          "sass-loader" //1. Turns sass into css
        ]
      },
      {
      test: /\.m?js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env', {
              modules: false,
              useBuiltIns: "usage",
              targets: {
                browsers: [
                  'Chrome >= 60',
                  'Safari >= 10.1',
                  'iOS >= 10.3',
                  'Firefox >= 54',
                  'Edge >= 15',
                ],
              },
            }],
          ],
        },
      },
    }],
  },
});

module.exports = [es5Config, es6Config];

The issue is now that I wanted webpack to import the polyfills only for the es5 build. and use usebuilins set to usage did not work to polyfill anything.

Am I probably using it wrong maybe something related to the the node_modules package??

So I am just importing the polyfills in the main file like:

import "core-js/stable";
import "regenerator-runtime/runtime";

How can I make these imports to be added only for the es5 build? Or how can I include polyfills/imports from webpack?

And extra question if anyone knows, how can I use the usebuiltins with "usage" correctly? Cause so far even thought the polifylls are added for my main file I still get errors for Symbols in IE11, for example.


Solution

  • I figured it out. here is the webpack config: common:

    const path = require("path");
    
    module.exports = {
      entry: {
        main: "./src/index.js",
        vendor: "./src/vendor.js"
      },
      module: {
        rules: [
          {
            test: /\.html$/,
            use: ["html-loader"]
          },
          {
            test: /\.(svg|png|jpg|gif)$/,
            use: {
              loader: "file-loader",
              options: {
                name: "[name].[hash].[ext]",
                outputPath: "imgs"
              }
            }
          }
        ]
      }
    };
    

    prod:

    const path = require("path");
    
    const common = require("./webpack.common");
    const merge = require("webpack-merge");
    const CleanWebpackPlugin = require("clean-webpack-plugin");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
    const TerserPlugin = require("terser-webpack-plugin");
    var HtmlWebpackPlugin = require("html-webpack-plugin");
    const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
    
    const es5Config = merge(common,{
      mode: "production",
      output: {
        filename: "[name].[contentHash].bundle.es5.js",
        path: path.resolve(__dirname, "dist")
      },
      optimization: {
        runtimeChunk: 'single',
        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('@', '')}`;
              },
            },
          },
        },
        minimizer: [
          new OptimizeCssAssetsPlugin(),
          new TerserPlugin(),
          new HtmlWebpackPlugin({
            filename: 'main.aspx',
            template: "./src/main.html",
            // minify: {
            //   removeAttributeQuotes: true,
            //   collapseWhitespace: true,
            //   removeComments: true
            // }
          }),
        ]
      },
      plugins: [
        new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        ],
    
      module: {
        rules: [
          {
            test: /\.scss$/,
            use: [
              MiniCssExtractPlugin.loader, //3. Extract css into files
              "css-loader", //2. Turns css into commonjs
              "sass-loader" //1. Turns sass into css
            ]
          },
          {
          test: /\.m?js$/,
          exclude: /node_modules/,
          //exclude: /node_modules\/(?!(\@pnp)\/).*/, 
          use: {
            loader: 'babel-loader',
            options: {
              //configFile : './es5.babel.config.json',
              presets: [
                ['@babel/preset-env', {
                  modules: false,
                  useBuiltIns: false,
                  targets: {
                    browsers: [
                      "IE 11"
                    ],
                  },
                }],
              ],
            },
          },
        }],
      },
    });
    
    const es6Config = merge(common, {
      mode: "production",
      output: {
        filename: "[name].[contentHash].bundle.es6.js",
        path: path.resolve(__dirname, "dist")
      },
      optimization: {
        runtimeChunk: 'single',
        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('@', '')}`;
              },
            },
          },
        },
        minimizer: [
          new OptimizeCssAssetsPlugin(),
          new TerserPlugin(),
          new HtmlWebpackPlugin({
            filename: 'main_es6.html',
            template: "./src/main.html",
            // minify: {
            //   removeAttributeQuotes: true,
            //   collapseWhitespace: true,
            //   removeComments: true
            // }
          }),
        ]
      },
      plugins: [
    
        new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
        new CleanWebpackPlugin(),
        ],
      module: {
        rules: [
          {
            test: /\.scss$/,
            use: [
              MiniCssExtractPlugin.loader, //3. Extract css into files
              "css-loader", //2. Turns css into commonjs
              "sass-loader" //1. Turns sass into css
            ]
          },
      ],
      },
    });
    module.exports = [ es5Config, es6Config];
    

    babel.config.json:

    {
      "plugins": [
        [
          "@babel/plugin-transform-runtime",
          {
            "absoluteRuntime": true,
            "helpers": true,
            "regenerator": true,
            "useESModules": false
          }
        ]
      ]
    }
    

    so this combined with the cdn polyfill works to load the polifylls only for IE11. it also has it's own build.

    The only issue here is that the resulting output will have separated files and the es5 build should have nomodule in all it's scripts. Also for the es6 all should have module. I then have to go and manually add those which I can easily make a custom template to handle that.

    But then the scripts for the polyfill is removed and I still have to manually merge the html files. Anyone knows how to handle that?

    edit: 1 - for the attributes you can use HtmlWebpackPlugin hooks () or ScriptExtHtmlWebpackPlugin to place the attributes in the tags.

    find some code with hooks here:

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    
    class es5TagTransformerHook {
      apply (compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      
          HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
            'es5TagTransformerHook', stacktraces
            async (data, cb) => {
              // Manipulate the content
              // data.html += 'The Magic Footer'
              // Tell webpack to move on
              data.bodyTags.forEach(t=>t.tagName === 'script'?t.attributes.nomodule = true:null)
    
              cb(null, data)
            }
          )
          
        })
      }
    }
    
    module.exports = es5TagTransformerHook
    

    2 - for merging the resulting html I ended up using this gist: https://gist.github.com/agoldis/21782f3b9395f78d28dce23e3b6ddd56