Reputation: 6392
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.
Will the below config produce and bundle Javascript that'll run on the maximum possible range of browsers? Is there any way to make it more compatible?
I have useBuiltins=usage
- will the webpack config below detect repeated imports, and tree shake? If not what do I need to change, or would useBuiltins=entry
and require('core-js');require('regenerator-runtime/runtime')
in my index.js
be better?
Using import
or require
to import bootstrap generates a larger file than the bootstrap distribution, even though I make no reference to it in the JS. How can I get tree-shaking working? Should I not use jquery via npm? EDIT: Tree shaking only happens on PROD builds, and seems to be working with the below configuration.
Can I safely use the latest jquery and rely on the polyfilling, rather than 1.12, which has security issues but I'm using as it works on much more browsers?
Can I remove @babel/cli
, as webpack is running babel? (It works, I just want to be sure I'm getting every ounce of polyfill and compatibility, happy to run babel CLI if better.)
Any other missed opportunities/recommendations?
(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
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