1
0
mirror of https://github.com/lancedikson/bowser synced 2026-10-15 15:40:52 +00:00

perf: trim bundled.js to the 2.14.1 polyfill set, fix stale docs

- Exclude the 10 core-js 3.0 modules with no counterpart in the
  @babel/polyfill set every released bundled.js shipped (concat/splice
  species fixes, Array#flat, Object.fromEntries, the __defineGetter__
  family, NodeList#forEach): 156.1 -> 153.2 kB (49.3 -> 48.4 kB gzip).
  babel rejects unknown exclude names, so a core-js rename fails the build.
- terser `passes: 5`, where output stops shrinking: es5.js 7.52 -> 7.49 kB
  gzip.
- New test: bundled.js must not install polyfills no release ever shipped
  (fails on the previous master build, passes on 2.14.1 and this one).
- README no longer claims named imports shrink the bundle; AGENTS.md
  describes the tsdown build and four-artifact acceptance tests;
  CONTRIBUTING points at the existing lint:check script.
This commit is contained in:
naorpeled
2026-09-27 22:26:39 +03:00
parent fd556800ee
commit 5249a7e4e8
6 changed files with 57 additions and 6 deletions

View File

@@ -38,5 +38,5 @@ If you'd like to contribute a change to `bowser`, modify the files in `src/`, an
$ npm install
$ npm run build #build
$ npm test #run tests
$ npm run lint #check lint rules
$ npm run lint:check #check lint rules
```

View File

@@ -14,7 +14,7 @@ Also supports User-Agent Client Hints via an optional `clientHints` parameter on
## Commands
- **Build**: `npm run build` (Webpack + Babel → `es5.js`, `bundled.js`)
- **Build**: `npm run build` (tsdown + Babel → UMD `es5.js` / `bundled.js` and ESM `bowser.mjs`, all ES5; see `tsdown.config.ts`)
- **Test**: `npm test` (AVA — runs both `test/unit/` and `test/acceptance/`)
- **Lint**: `npm run lint:check` / `npm run lint:fix`
@@ -72,7 +72,7 @@ Returns `true`/`false` if the browser matches, or `undefined` if the browser isn
## Testing Details
- **Acceptance tests** (`test/acceptance/test-list-of-ua.js`): Loads `useragentstrings.yml`, tests every UA against both `src/bowser.js` (ES6) and `es5.js` (built) — so **build before running tests** if you changed source.
- **Acceptance tests** (`test/acceptance/test-list-of-ua.js`): Loads `useragentstrings.yml`, tests every UA against `src/bowser.js` and all three built artifacts (`es5.js`, `bundled.js`, `bowser.mjs`) — so **build before running tests** if you changed source.
- **Unit tests** (`test/unit/`): Test individual Parser/Bowser/Utils methods. Use `new Parser(UA, true)` (skipParsing=true) for isolated method testing.
- Test framework: AVA with Babel transpilation via `@babel/register`.

View File

@@ -39,8 +39,9 @@ import * as Bowser from "bowser"; // TypeScript
import Bowser from "bowser"; // ES6 (and TypeScript with --esModuleInterop enabled)
```
The ES module build also exposes `parse` and `getParser` as named exports, so
you can import just the part you use and let your bundler drop the rest:
The ES module build also exposes `parse` and `getParser` as named exports.
Detection needs every parser either way, so named imports are a matter of
style, not bundle size — but an import that ends up unused is dropped entirely:
```javascript
import { getParser, parse } from "bowser";

View File

@@ -11,6 +11,8 @@
* `core-js/es` rather than `core-js/stable`: `stable` is `es` plus every web
* polyfill, and the extra ones — `URL`, `URLSearchParams`, `queueMicrotask` —
* were never in any released `bundled.js`, yet cost ~20 kB (~7 kB gzipped).
* The few `es` modules `@babel/polyfill` did not have are excluded in
* `tsdown.config.ts` (`NOT_IN_BABEL_POLYFILL`).
* `test/acceptance/test-es5-runtime.js` pins the `@babel/polyfill` surface.
*
* See `./es5.js` for why this only re-exports the default.

View File

@@ -179,3 +179,26 @@ test('bundled.js still installs everything @babel/polyfill did', (t) => {
vm.runInContext(fs.readFileSync(path.join(root, 'bundled.js'), 'utf8'), context);
t.deepEqual(BABEL_POLYFILL_SURFACE.filter((ref) => !present(ref)), [], 'bundled.js no longer installs these');
});
/**
* The other side of the contract: polyfills no released `bundled.js` ever
* shipped stay out, because each one is pure weight for existing consumers.
* Adding one back should be a deliberate decision that updates this list, not
* a side effect of switching a core-js entry point.
*/
const NEVER_SHIPPED = [
'this.URL', 'this.URLSearchParams', 'this.queueMicrotask',
'Array.prototype.flat', 'Object.fromEntries', 'NodeList.prototype.forEach',
];
test('bundled.js does not install polyfills no release ever shipped', (t) => {
const context = createEs5Context();
vm.runInContext(NEVER_SHIPPED
.map((ref) => `try { delete ${ref}; } catch (e) {}`)
.join('\n'), context);
vm.runInContext('this.NodeList = function NodeList() {};', context);
const present = (ref) => vm.runInContext(`typeof (${ref})`, context) !== 'undefined';
vm.runInContext(fs.readFileSync(path.join(root, 'bundled.js'), 'utf8'), context);
t.deepEqual(NEVER_SHIPPED.filter(present), []);
});

View File

@@ -38,11 +38,34 @@ const legacyBabel = (useBuiltIns: false | 'entry') => babel({
modules: false,
loose: true,
useBuiltIns,
...(useBuiltIns ? { corejs: '3' } : {}),
...(useBuiltIns ? { corejs: '3', exclude: NOT_IN_BABEL_POLYFILL } : {}),
targets: legacyTargets,
}]],
});
/**
* The only core-js 3.0 `es` / web modules with no counterpart in the
* `@babel/polyfill` (core-js 2) set that every released `bundled.js` shipped:
* the `concat`/`splice` species fixes, `Array#flat`, `Object.fromEntries`, the
* Annex B `__defineGetter__` family and `NodeList#forEach`. Excluding them makes
* the payload exactly that set again, 2.8 kB (0.9 kB gzipped) smaller.
*
* babel rejects unknown names here, so a rename in core-js fails the build
* rather than silently shipping the module again.
*/
const NOT_IN_BABEL_POLYFILL = [
'es.array.concat',
'es.array.flat',
'es.array.splice',
'es.array.unscopables.flat',
'es.object.define-getter',
'es.object.define-setter',
'es.object.from-entries',
'es.object.lookup-getter',
'es.object.lookup-setter',
'web.dom-collections.for-each',
];
/**
* Lowers the *emitted chunk* to ES5, after bundling and before terser.
*
@@ -108,6 +131,8 @@ const terser = () => ({
// IE 8: reserved words as property names must stay quoted.
ie8: true,
safari10: true,
// Output stops shrinking at 5 passes (measured; 6+ is byte-identical).
compress: { passes: 5 },
format: {
// Keep the `/*!` banner.
comments: /^!/,