Chris
Chris

Reputation: 6392

BabelJS and Webpack config for maximum possible compatibility

My website must work with the most basic phone browsers imaginable, because my user base is rural Ethiopian children on very, very basic handsets. (I am using jquery to save handset battery, as most are 'recycled', second/third/fourth-hand and ancient, and I'm being extremely careful about data costs.)

I am trying to set up Babel + Webpack to transpile for the lowest possible supportable target, but I misunderstand the Babel docs, (eg, I started with @babel/preset-env and no targets, as I assumed that not targeting meant maximum compatibility, but this doesn't polyfill), and can't test against my enormous range of target handsets and browsers.

(If relevant, I do not need any chunking - this is a simple app and I am caching indefinitely. I am writing this into a Django static folder, and Django + whitenoise are handling filename fingerprinting and HTTP compression. I will at some point add JS unit tests. I am importing bootstrap JS for polyfills and tree-shaking (although Bootstrap doesn't seem to be shaking), but loading the bootstrap CSS directly from the HTML to avoid cache misses when I update the app.)

packages.json:

{
  ...
  "scripts": {
    "start": "webpack-dev-server --open --mode development",
    "build": "webpack --mode production",
  },
  "devDependencies": {
    "@babel/cli": "^7.10.1",
    "@babel/core": "^7.10.2",
    "@babel/plugin-proposal-class-properties": "^7.10.1",
    "@babel/preset-env": "^7.10.2",
    "axios": "^0.19.2",
    "babel-loader": "^8.1.0",
    "bootstrap": "^4.4.1",
    "jquery": "^1.12.4",  // insecure old version but more compatible
    "popper.js": "^1.16.1",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "@babel/polyfill": "^7.10.1",
    "core-js": "^3.6.5"
  }
}

.babelrc:

{
  "presets": [
    [
      "@babel/env",
      {
        "targets": "cover 100%",
        "useBuiltIns": "usage",
        "corejs": "3.6.5"
      }
    ]
  ],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

webpack.config.js:

const path = require('path');

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  output: {
    filename: 'app.js',
    publicPath: "/static/",
    path: path.join(__dirname, '../djangoproj/app/static/app')
  },
  devServer: {
    writeToDisk: true, // Django serves the content
  }
};

index.js:

import $ from 'jquery';
import bootstrap from 'bootstrap'
import popper from 'popper.js'
import {Controller} from './controller';

$(document).ready(function() {
  const app = new Controller()
})

controller.js:

import {View} from './view';
import {ActivityStore, ElementStore} from './store';
import {Api} from './api';

export class Controller {
  constructor() {
    this.state = {}
    this.api = new Api(config)

// and so on..

Update: I have decided not to progressively polyfill (using <script type="module" ..) as @tfr recommends below, as it is more important to me to test for the lowest phones than optimise newer phones. This is more likely if I'm running the polyfills on my more modern test devices. That said, core-js claims to only polyfill if necessary, so I'm not sure whether nomodules really makes a difference beyond bundle size (so much of my understanding of this technology is choosing which bit of info I trust my understanding of more). I also decided to load Bootstrap and Popper direct from the browser rather than bundled. I am looking into generating a vendor.js but not sure there are any advantages, except perhaps that they'll load before the polyfills in my bundle.

Enormous thanks.

Upvotes: 4

Views: 1494

Answers (1)

tfrei
tfrei

Reputation: 299

Normally the best way would be to bundle dual (modern browser and legacy) the same time, so you don't have to polyfill modern devices. Take a look at this working polyfill example.

Thats how you could load es6 for modern and es5 bundle for legacy browser:

  <!-- main bundle -->
  <script type="module" src="/assets/scripts/main.js"></script>
  <!-- polyfilled legacy browser -->
  <script nomodule src="/assets/scripts/main.legacy.js" defer="defer"></script>

And here the main answer to your question:

Babel Config
============

const legacy = {
  presets: [
    ["@babel/preset-env", {
      targets: {
        browsers: [
          "> 1% in ET", // make sure its the right country code
          "ie >= 6",
          "not dead"
        ]
      },
      useBuiltIns: "usage",
      corejs: 3,
    }]
  ],
  plugins: [
    "@babel/plugin-syntax-dynamic-import",
    "transform-eval"
  ],
  comments: false,
  cacheDirectory: path.join(CACHE_PATH, 'babel-loader')
};


Webpack Config
==============

 const prod = {
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js$/,
        exclude: [
          /node_modules/
        ],
        use: {
          loader: 'eslint-loader',
        },
      },
      {
        test: /\.js$/,
        exclude: [
          /node_modules/
        ],
        loader: "babel-loader",
        options: babelConfig
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.json'],
  }
};

snippets from https://github.com/unic/darvin-webpack-boilerplate/blob/master/webpack/settings/javascript-legacy/index.js

Upvotes: 1

Related Questions