Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Browserslist config with future support #3728

Merged
merged 9 commits into from
Jul 5, 2023

Conversation

colinrotherham
Copy link
Contributor

@colinrotherham colinrotherham commented Jun 1, 2023

Support for IE11 -ms-* vendor prefixes came up recently on Slack following 478af39

Based on thinkings from @querkmachine in The future of browser support in GOV.UK Frontend (internal) I've put a draft PR together of some Browserslist config changes we might want to make

Up for discussion as part of:

Browserslist config environments

I've included separate Browserslist environments for JavaScripts, Stylesheets and Node.js:

# This list builds on the GOV.UK service manual's browser testing recommendations
# https://www.gov.uk/service-manual/technology/designing-for-different-browsers-and-devices

[javascripts]
> 0.1% in GB and supports es6-module
last 6 Chrome versions
last 6 Firefox versions
last 6 Edge versions
last 2 Samsung versions
Firefox ESR
Safari >= 11 and not Safari < 11
iOS >= 11 and not iOS < 11

[stylesheets]
> 0.1% in GB and not dead
last 6 Chrome versions
last 6 Firefox versions
last 6 Edge versions
last 2 Samsung versions
Firefox ESR
Safari >= 11
iOS >= 11
ie 11

[node]
node 18

For example:

  • Environment [stylesheets] restores Internet Explorer 11 to maintain -ms-* vendor prefixes
  • Environment [javascripts] removes Safari 9-10 that fails 'noModule' in HTMLScriptElement.prototype

Babel config browserslistEnv

For the webpack example, Babel also used the Browserslist config so I've set browserslistEnv to ensure it switches to the new javascripts environment. See the Babel spike for how we'd used the same approach for govuk-frontend too:

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 1, 2023 11:22 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 5, 2023 07:42 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 12, 2023 12:25 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 12, 2023 16:09 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 19, 2023 08:30 Inactive
@querkmachine
Copy link
Member

querkmachine commented Jun 19, 2023

Out of curiosity, why does the JavaScript configuration specify:

Safari >= 12 and not Safari < 11
iOS >= 12.1 and not iOS < 11

and not just

Safari >= 12
iOS >= 12.1

As far as I can tell from playing with browsersl.ist, there isn't any difference between the browsers returned by the first query vs. the second query.

@colinrotherham
Copy link
Contributor Author

Thanks for taking a look @querkmachine

It was a safety net for the > 0.1% in GB returning older Safari releases (see their Query Composition notes)

You'll see it unintentionally bringing in Safari on iOS 8.1-8.4, 9.3 etc where only Safari 9+ actually supports ES2015 modules, but worse still only Safari 11+ supports nomodule scripts

So by narrowing it down to > 0.1% in GB and supports es6-module we still need to filter out Safari 10.3 on iOS

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 19, 2023 17:39 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 20, 2023 12:47 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 20, 2023 14:49 Inactive
@colinrotherham colinrotherham marked this pull request as ready for review June 20, 2023 15:27
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 23, 2023 10:14 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 26, 2023 15:54 Inactive
@colinrotherham colinrotherham changed the base branch from main to jest-dynamic-import June 26, 2023 16:20
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 26, 2023 16:20 Inactive
@colinrotherham colinrotherham self-assigned this Jun 27, 2023
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 29, 2023 12:04 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 29, 2023 12:36 Inactive
Base automatically changed from jest-dynamic-import to main June 29, 2023 16:42
@colinrotherham
Copy link
Contributor Author

Revised to lower Safari 12 to Safari 11 to match our "cut the mustard" check:

'noModule' in HTMLScriptElement.prototype

Comment on lines 5 to 12
> 0.1% in GB and supports es6-module
last 6 Chrome versions
last 6 Firefox versions
last 6 Edge versions
last 2 Samsung versions
Safari >= 9
iOS >= 9
Firefox ESR
Safari >= 11 and not Safari < 11
iOS >= 11 and not iOS < 11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the decisions we made on Tuesday, I'd expect this to just be:

[javascripts]
supports es6-module

By restricting it to only browsers with >0.1% marketshare we're back in a place where we may ship JS that is not parseable in all browsers that support type="module"?

Additionally, is there benefit in listing out specific browsers when we know they'll be covered by supports es6-module anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@36degrees Of course! I'm torn on wanting to let browsers go from Grade A, to Grade B, to Grade C

Using the query > 0.1% in GB and supports es6-module lets us gracefully (in future) go from 100% polyfill coverage, to letting older browsers slowly fail feature detection and drop down a grade

Let me know if you'd rather have a fixed point in time instead—I'll fit in with you

(Adding specific browsers was to prevent the sliding scale from going too far)

Copy link
Contributor Author

@colinrotherham colinrotherham Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch up @36degrees

For safety let's go with supports es6-module

Should we find that Babel is adding some weighty transforms/plugins that we could instead guard with "feature detection", we'll exclude them in the Babel config instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most interesting side effect of this change is over in:

We're now seeing all let and const transformed back to var for Safari < 11 😮

Odd, but let's dig further

Babel reports that the transform was applied by @babel/plugin-transform-block-scoping to fix slowdowns in Safari. Appears that we set safari10: true in Terser for the same reason

You might find ESBuild's explanation is easier to follow than the WebKit bug

Top-level var

In JavaScript, let, const, and class declarations all introduce TDZ checks while var declarations do not. Since bundling typically merges many modules into a single very large top-level scope, the performance impact of these TDZ checks can be pretty severe. Converting top-level let, const, and class declarations into var helps automatically make your code faster.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding debug: true to the Babel @babel/preset-env config you'll see the following:

Using plugins:
  transform-unicode-sets-regex { chrome < 112, edge < 112, firefox, ios, opera < 98, safari, samsung }
  proposal-class-static-block { chrome < 94, edge < 94, firefox < 93, ios, opera < 80, safari, samsung < 17 }
  proposal-private-property-in-object { chrome < 91, edge < 91, firefox < 90, ios < 15, opera < 77, safari < 15, samsung < 16 }
  proposal-class-properties { chrome < 74, edge < 79, firefox < 90, ios < 15, opera < 62, safari < 14.1, samsung < 11 }
  proposal-private-methods { chrome < 84, edge < 84, firefox < 90, ios < 15, opera < 70, safari < 15, samsung < 14 }
  proposal-numeric-separator { chrome < 75, edge < 79, firefox < 70, ios < 13, opera < 62, safari < 13, samsung < 11 }
  proposal-logical-assignment-operators { chrome < 85, edge < 85, firefox < 79, ios < 14, opera < 71, safari < 14, samsung < 14 }
  proposal-nullish-coalescing-operator { chrome < 80, edge < 80, firefox < 72, ios < 13.4, opera < 67, safari < 13.1, samsung < 13 }
  proposal-optional-chaining { chrome < 91, edge < 91, firefox < 74, ios < 13.4, opera < 77, safari < 13.1, samsung < 16 }
  proposal-json-strings { chrome < 66, edge < 79, firefox < 62, ios < 12, opera < 53, safari < 12, samsung < 9 }
  proposal-optional-catch-binding { chrome < 66, edge < 79, ios < 11.3, opera < 53, safari < 11.1, samsung < 9 }
  transform-parameters { edge < 18, ios, safari }
  proposal-async-generator-functions { chrome < 63, edge < 79, ios < 12, opera < 50, safari < 12 }
  proposal-object-rest-spread { edge < 79, ios < 11.3, safari < 11.1 }
  transform-dotall-regex { chrome < 62, edge < 79, firefox < 78, ios < 11.3, opera < 49, safari < 11.1 }
  proposal-unicode-property-regex { chrome < 64, edge < 79, firefox < 78, ios < 11.3, opera < 51, safari < 11.1, samsung < 9 }
  transform-named-capturing-groups-regex { chrome < 64, edge < 79, firefox < 78, ios < 11.3, opera < 51, safari < 11.1, samsung < 9 }
  transform-async-to-generator { ios < 11, safari < 11 }
  transform-template-literals { ios < 13, safari < 13 }
  transform-function-name { edge < 79 }
  transform-unicode-regex { ios < 12, safari < 12 }
  transform-block-scoping { ios < 11, safari < 11 }
  proposal-export-namespace-from { chrome < 72, edge < 79, firefox < 80, ios, opera < 60, safari, samsung < 11.0 }
  syntax-dynamic-import
  syntax-top-level-await
  syntax-import-meta

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! Any thoughts on what we should do?

I guess we might want to do some performance testing on the affected Safari versions to see what the impact would be of disabling that transformation?

Copy link
Contributor Author

@colinrotherham colinrotherham Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the config for now and assume (future) Babel will save our bacon many more times 😓

But ideally let's see if Rollup avoids the issue already, since those Babel transforms might be 100% unnecessary. We might not even have any code that triggers the slow TDZ ("temporal dead zone") bug

This PR is good to go though

packages/govuk-frontend/.browserslistrc Show resolved Hide resolved
packages/govuk-frontend-review/.browserslistrc Outdated Show resolved Hide resolved
Comment on lines 7 to 21
const browserslistEnv = !api.env('test')
? 'javascripts'
: 'node'

return {
presets: [
['@babel/preset-env', {
browserslistEnv,
loose: browserslistEnv !== 'node',

// Transform ES modules for Node.js
modules: browserslistEnv === 'node' ? 'auto' : false
}]
]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that all of the config for '@babel/preset-env' depends on the browsersListEnv, I'm wondering if it might be simpler to just return two different objects? Especially as there's a lot of negation going on at the minute which can make it harder to follow.

Suggested change
const browserslistEnv = !api.env('test')
? 'javascripts'
: 'node'
return {
presets: [
['@babel/preset-env', {
browserslistEnv,
loose: browserslistEnv !== 'node',
// Transform ES modules for Node.js
modules: browserslistEnv === 'node' ? 'auto' : false
}]
]
}
let presetEnvConfig
if (api.env('test')) {
presetEnvConfig = {
browsersListEnv: 'node',
modules: false
}
} else {
presetEnvConfig = {
browsersListEnv: 'javascripts',
loose: true
}
}
return {
presets: [
['@babel/preset-env', presetEnvConfig]
]
}

Or even:

Suggested change
const browserslistEnv = !api.env('test')
? 'javascripts'
: 'node'
return {
presets: [
['@babel/preset-env', {
browserslistEnv,
loose: browserslistEnv !== 'node',
// Transform ES modules for Node.js
modules: browserslistEnv === 'node' ? 'auto' : false
}]
]
}
if (api.env('test')) {
return {
presets: [
['@babel/preset-env', {
browsersListEnv: 'node',
modules: false
}]
]
}
} else {
return {
presets: [
['@babel/preset-env', {
browsersListEnv: 'javascripts',
loose: true
}]
]
}
}

Copy link
Contributor Author

@colinrotherham colinrotherham Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you rather we did?

We could end up adding a third development config with more lightweight transforms, since most development only happens in latest Chrome/Safari/Firefox

I'll have a think on Monday

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@36degrees I've removed !== 'node' to help keep examples/webpack/babel.config.js short and sweet

But only because we're going to need to duplicate the config for the govuk-frontend package over in:

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 June 30, 2023 19:17 Inactive
This will help in future to manage supported browsers/environments from a single config per workspace
But we see `npx browserslist` has removed these browsers:

* UC Browser for Android (India, Indonesia, China)
* QQ Browser (China)
Samsung Internet `last 2 Samsung versions` already targets 6 months of updates
We use an ES2015 “cut the mustard” check `'noModule' in HTMLScriptElement.prototype` which was available in Safari 11+

https://caniuse.com/mdn-html_elements_script_nomodule

This differs to the Service Manual (June 2022) which suggests testing in Safari for macOS 12+ and Safari for iOS 12.1+

https://www.gov.uk/service-manual/technology/designing-for-different-browsers-and-devices
Browsers also get `{ loose: true }` to use smaller transforms

See: https://babeljs.io/docs/babel-preset-env#loose
This change configures a new `stylesheets` Browserslist environment for PostCSS (including Autoprefixer) which means we can selectively drop IE11 elsewhere in future
The default browsers that load our CSS differ to those that run our JavaScript, so we don’t need Babel transforms for IE11 and other legacy browsers

We’ll start by targetting `supports es6-module` and can optionally exclude Babel transforms or plugins as required, should we prefer feature detection instead
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-3728 July 5, 2023 13:29 Inactive
@colinrotherham colinrotherham merged commit b7f5e9c into main Jul 5, 2023
@colinrotherham colinrotherham deleted the browserslist-support branch July 5, 2023 16:04
colinrotherham added a commit that referenced this pull request Jul 11, 2023
We're now seeing all `let` and `const` transformed back to `var` for slowdowns affecting Safari < 11

See comment: #3728 (comment)
colinrotherham added a commit to alphagov/govuk-design-system that referenced this pull request Aug 16, 2023
This change configures a new `stylesheets` Browserslist environment for PostCSS (including Autoprefixer) which means we can selectively drop IE11 elsewhere in future

See: alphagov/govuk-frontend#3728
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

4 participants