mirror of
https://github.com/lancedikson/bowser
synced 2026-10-09 12:22:12 +00:00
Dual packaging, without breaking existing consumers (#628)
Co-authored-by: Yasumasa Ashida <ys.ashida@gmail.com>
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
parser: babel-eslint
|
|
||||||
extends: airbnb-base
|
|
||||||
rules:
|
|
||||||
no-underscore-dangle: 0
|
|
||||||
no-void: 0
|
|
||||||
import/extensions:
|
|
||||||
- 'error'
|
|
||||||
- 'ignorePackages'
|
|
||||||
- {js: 'always'}
|
|
||||||
import/prefer-default-export: 1
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- ava
|
|
||||||
- import
|
|
||||||
33
.github/workflows/merge-to-master.yml
vendored
33
.github/workflows/merge-to-master.yml
vendored
@@ -8,30 +8,29 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [12.16.3]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Get branch name (merge)
|
- name: Get branch name
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get branch name (pull request)
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
shell: bash
|
|
||||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
# tsdown requires Node ^22.18.0 || >=24.11.0. The published artifacts are
|
||||||
|
# still ES5 — see the `pack-smoke` job in pull-request.yml.
|
||||||
|
- name: Set up node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: "24"
|
||||||
- run: npm i -g nyc@15
|
cache: pnpm
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: nyc npm test && nyc report --reporter=text-lcov | ./node_modules/coveralls/bin/coveralls.js
|
- run: pnpm build
|
||||||
|
|
||||||
|
# `pnpm test` already runs ava under nyc; this only reports the result.
|
||||||
|
- run: pnpm test && pnpm exec nyc report --reporter=text-lcov | pnpm exec coveralls
|
||||||
env:
|
env:
|
||||||
COVERALLS_SERVICE_NAME: GithubActions
|
COVERALLS_SERVICE_NAME: GithubActions
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||||
|
|||||||
43
.github/workflows/publish.yml
vendored
43
.github/workflows/publish.yml
vendored
@@ -21,13 +21,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
# tsdown requires Node ^22.18.0 || >=24.11.0. This only affects how the
|
||||||
|
# package is built; the artifacts it emits are still ES5.
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 12.16.3
|
node-version: "24"
|
||||||
- uses: actions/cache@v4
|
cache: pnpm
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
|
|
||||||
# Store the release version (from release tag or manual input)
|
# Store the release version (from release tag or manual input)
|
||||||
- name: Set release version
|
- name: Set release version
|
||||||
run: |
|
run: |
|
||||||
@@ -38,14 +42,33 @@ jobs:
|
|||||||
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
echo "Release triggered from tag: ${GITHUB_REF#refs/*/}"
|
echo "Release triggered from tag: ${GITHUB_REF#refs/*/}"
|
||||||
fi
|
fi
|
||||||
- run: npm ci
|
|
||||||
- run: npm version $RELEASE_VERSION --no-git-tag-version
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: npm run build
|
- run: npm version $RELEASE_VERSION --no-git-tag-version --allow-same-version
|
||||||
|
- run: pnpm build
|
||||||
|
|
||||||
|
# Guard the release: the same checks the PR workflow runs, against the
|
||||||
|
# artifacts actually about to be published.
|
||||||
|
- run: pnpm test
|
||||||
|
- run: pnpm exec publint
|
||||||
|
- run: pnpm exec attw --pack . --profile node16 --entrypoints .
|
||||||
|
|
||||||
|
# Upload only what `files` publishes, plus the manifest. The previous
|
||||||
|
# `path: .` also shipped node_modules through the artifact store.
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: .
|
path: |
|
||||||
|
package.json
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
es5.js
|
||||||
|
bundled.js
|
||||||
|
bowser.mjs
|
||||||
|
index.d.ts
|
||||||
|
index.d.mts
|
||||||
|
src
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: build
|
||||||
@@ -61,4 +84,4 @@ jobs:
|
|||||||
- name: Update npm to latest (trusted publishing requires npm >= 11.5.1)
|
- name: Update npm to latest (trusted publishing requires npm >= 11.5.1)
|
||||||
run: npm install -g npm@latest
|
run: npm install -g npm@latest
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
run: npm publish --provenance --access public
|
run: npm publish --provenance --access public
|
||||||
|
|||||||
140
.github/workflows/pull-request.yml
vendored
140
.github/workflows/pull-request.yml
vendored
@@ -3,42 +3,128 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
jobs:
|
# Every job only reads the repo and moves artifacts between jobs; none of them
|
||||||
test:
|
# needs write access to anything.
|
||||||
runs-on: ubuntu-latest
|
permissions:
|
||||||
strategy:
|
contents: read
|
||||||
matrix:
|
|
||||||
node: [12.16.3]
|
|
||||||
name: Node ${{ matrix.node }}
|
|
||||||
steps:
|
|
||||||
- name: "Checkout latest code"
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
- name: Set up node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
- name: Build
|
|
||||||
run: npm run build
|
|
||||||
- name: Run tests
|
|
||||||
run: npm run test
|
|
||||||
|
|
||||||
lint:
|
env:
|
||||||
name: "ESLint"
|
# tsdown requires Node ^22.18.0 || >=24.11.0. This is the *build* toolchain
|
||||||
|
# only — see the `pack-smoke` job for the versions the published package
|
||||||
|
# itself has to keep working on.
|
||||||
|
BUILD_NODE_VERSION: "24"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: "Build & test"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout latest code
|
- name: Checkout latest code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Set up node
|
- name: Set up node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: ${{ env.BUILD_NODE_VERSION }}
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
run: npm run lint:check
|
run: pnpm lint:check
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
# package.json intentionally carries no `version` — it is stamped at
|
||||||
|
# release time by publish.yml. `npm pack` needs one, so use a throwaway.
|
||||||
|
- name: Pack
|
||||||
|
run: |
|
||||||
|
npm version 0.0.0-ci --no-git-tag-version --allow-same-version
|
||||||
|
mkdir -p tarball
|
||||||
|
npm pack --pack-destination ./tarball
|
||||||
|
|
||||||
|
- name: Upload package tarball
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: package-tarball
|
||||||
|
path: tarball/*.tgz
|
||||||
|
|
||||||
|
pack-smoke:
|
||||||
|
# Installs the packed tarball and exercises every documented entry point.
|
||||||
|
# bowser publishes ES5 artifacts and has no `engines` field, so the package
|
||||||
|
# must keep working far below the version we build on.
|
||||||
|
name: "Consumer smoke (Node ${{ matrix.node }})"
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node: ["12.16.3", "14", "18", "20", "24"]
|
||||||
|
steps:
|
||||||
|
- name: Checkout latest code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
- name: Download package tarball
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: package-tarball
|
||||||
|
path: tarball
|
||||||
|
|
||||||
|
- name: Set up node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
# No install step: bowser has no runtime dependencies and the smoke test
|
||||||
|
# only uses Node built-ins, so this runs on every version above.
|
||||||
|
- name: Run package smoke test
|
||||||
|
run: node test/package/smoke.cjs tarball/*.tgz
|
||||||
|
|
||||||
|
package-lint:
|
||||||
|
name: "Package manifest"
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout latest code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
|
- name: Set up pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Set up node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.BUILD_NODE_VERSION }}
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Stamp a throwaway version
|
||||||
|
run: npm version 0.0.0-ci --no-git-tag-version --allow-same-version
|
||||||
|
|
||||||
|
- name: publint
|
||||||
|
run: pnpm exec publint
|
||||||
|
|
||||||
|
# Scoped to the root entrypoint. The legacy `bowser/src/*` subpaths are
|
||||||
|
# untyped ESM sources in a CJS package — true on every published version
|
||||||
|
# of bowser, and preserved here on purpose.
|
||||||
|
- name: Are the types wrong?
|
||||||
|
run: pnpm exec attw --pack . --profile node16 --entrypoints .
|
||||||
|
|||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -2,6 +2,13 @@ node_modules/
|
|||||||
.idea/
|
.idea/
|
||||||
.nyc_output
|
.nyc_output
|
||||||
coverage
|
coverage
|
||||||
dist
|
|
||||||
bundled.js*
|
# Build output (see tsdown.config.ts). Anchored to the repo root on purpose —
|
||||||
es5.js*
|
# unanchored these would also match the build entry points in build/entries/.
|
||||||
|
/bundled.js*
|
||||||
|
/es5.js*
|
||||||
|
/bowser.mjs*
|
||||||
|
|
||||||
|
# `npm pack` output, used by the package smoke test
|
||||||
|
*.tgz
|
||||||
|
/tarball/
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -27,7 +27,9 @@ _For legacy code, check out the [1.x](https://github.com/bowser-js/bowser/tree/v
|
|||||||
|
|
||||||
# Use cases
|
# Use cases
|
||||||
|
|
||||||
First of all, require the library. This is a UMD Module, so it will work for AMD, TypeScript, ES6, and CommonJS module systems.
|
First of all, require the library. Bowser is a dual package: `require` resolves
|
||||||
|
to a UMD build (which also works for AMD and as a plain `<script>` tag), and
|
||||||
|
`import` resolves to a real ES module.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const Bowser = require("bowser"); // CommonJS
|
const Bowser = require("bowser"); // CommonJS
|
||||||
@@ -37,11 +39,30 @@ import * as Bowser from "bowser"; // TypeScript
|
|||||||
import Bowser from "bowser"; // ES6 (and TypeScript with --esModuleInterop enabled)
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { getParser, parse } from "bowser";
|
||||||
|
|
||||||
|
const browser = getParser(window.navigator.userAgent);
|
||||||
|
```
|
||||||
|
|
||||||
|
Loaded from a CDN or a `<script>` tag, Bowser attaches itself to the global as
|
||||||
|
`bowser` (lowercase):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://unpkg.com/bowser@2/es5.js"></script>
|
||||||
|
<script>
|
||||||
|
console.log(bowser.parse(window.navigator.userAgent));
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
By default, the exported version is the *ES5 transpiled version*, which **do not** include any polyfills.
|
By default, the exported version is the *ES5 transpiled version*, which **do not** include any polyfills.
|
||||||
|
|
||||||
In case you don't use your own `babel-polyfill` you may need to have pre-built bundle with all needed polyfills.
|
In case you don't use your own polyfills you may need to have pre-built bundle with all needed polyfills.
|
||||||
So, for you it's suitable to require bowser like this: `require('bowser/bundled')`.
|
So, for you it's suitable to require bowser like this: `require('bowser/bundled')`.
|
||||||
As the result, you get a ES5 version of bowser with `babel-polyfill` bundled together.
|
As the result, you get a ES5 version of bowser with `core-js` polyfills bundled together.
|
||||||
|
|
||||||
You may need to use the source files, so they will be available in the package as well.
|
You may need to use the source files, so they will be available in the package as well.
|
||||||
|
|
||||||
|
|||||||
16
build/entries/bundled.js
Normal file
16
build/entries/bundled.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* UMD entry point for `bundled.js` — same as `es5.js`, but with the polyfills
|
||||||
|
* needed by the browser targets baked in.
|
||||||
|
*
|
||||||
|
* `core-js/stable` + `regenerator-runtime/runtime` is the core-js@3 equivalent
|
||||||
|
* of the deprecated `@babel/polyfill` that this bundle used to be built from.
|
||||||
|
* `@babel/preset-env`'s `useBuiltIns: 'entry'` rewrites the `core-js/stable`
|
||||||
|
* import below into just the polyfills the configured targets actually need.
|
||||||
|
*
|
||||||
|
* See `./es5.js` for why this only re-exports the default.
|
||||||
|
*/
|
||||||
|
import 'core-js/stable';
|
||||||
|
import 'regenerator-runtime/runtime';
|
||||||
|
import Bowser from '../../src/bowser.js';
|
||||||
|
|
||||||
|
export default Bowser;
|
||||||
15
build/entries/es5.js
Normal file
15
build/entries/es5.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* UMD entry point for `es5.js`.
|
||||||
|
*
|
||||||
|
* `src/bowser.js` also has named exports (`parse`, `getParser`) so that ESM
|
||||||
|
* consumers can `import { getParser } from 'bowser'`. A UMD bundle with named
|
||||||
|
* exports would expose `module.exports = { default, parse, getParser }`, which
|
||||||
|
* would break every existing `require('bowser')` and `window.bowser` consumer.
|
||||||
|
*
|
||||||
|
* Re-exporting only the default here keeps the UMD output at
|
||||||
|
* `module.exports = Bowser` / `window.bowser = Bowser`. The class carries
|
||||||
|
* `parse` and `getParser` as static methods, so nothing is lost.
|
||||||
|
*/
|
||||||
|
import Bowser from '../../src/bowser.js';
|
||||||
|
|
||||||
|
export default Bowser;
|
||||||
36
eslint.config.mjs
Normal file
36
eslint.config.mjs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import importPlugin from 'eslint-plugin-import';
|
||||||
|
import { configs, plugins } from 'eslint-config-airbnb-extended';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
importPlugin.flatConfigs.recommended,
|
||||||
|
// Stylistic plugin
|
||||||
|
plugins.stylistic,
|
||||||
|
// Import X plugin
|
||||||
|
plugins.importX,
|
||||||
|
// Airbnb base recommended config
|
||||||
|
...configs.base.recommended,
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,mjs,cjs}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-underscore-dangle': 0,
|
||||||
|
'no-void': 0,
|
||||||
|
'import/prefer-default-export': 1,
|
||||||
|
'import-x/extensions': [
|
||||||
|
'error',
|
||||||
|
'always',
|
||||||
|
{
|
||||||
|
ignorePackages: true,
|
||||||
|
js: 'always',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@stylistic/max-len': 0,
|
||||||
|
'@stylistic/arrow-parens': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
33
index.d.mts
Normal file
33
index.d.mts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// ESM type definitions for Bowser v2.
|
||||||
|
//
|
||||||
|
// `index.d.ts` uses `export =`, which describes the CommonJS/UMD shape of
|
||||||
|
// `es5.js` (`module.exports = Bowser`). It is reached via the `require`
|
||||||
|
// condition and stays the source of truth for the type declarations.
|
||||||
|
//
|
||||||
|
// This file describes `bowser.mjs`, which is a real ES module: it has a default
|
||||||
|
// export *and* the `parse` / `getParser` named exports. It is reached via the
|
||||||
|
// `import` condition. Declaring it separately is what keeps the types honest
|
||||||
|
// for `moduleResolution: node16`/`bundler` consumers — reusing `index.d.ts` for
|
||||||
|
// both conditions would describe an ES module with CommonJS types.
|
||||||
|
|
||||||
|
import Bowser = require('./index.js');
|
||||||
|
|
||||||
|
export default Bowser;
|
||||||
|
|
||||||
|
export declare const parse: typeof Bowser.parse;
|
||||||
|
export declare const getParser: typeof Bowser.getParser;
|
||||||
|
|
||||||
|
// BROWSER_MAP / ENGINE_MAP / OS_MAP / PLATFORMS_MAP are *not* named exports of
|
||||||
|
// bowser.mjs — they exist only as static getters on the class. Declaring them
|
||||||
|
// here would let TypeScript accept `import { BROWSER_MAP } from 'bowser'`,
|
||||||
|
// which throws at runtime. Reach them via the default export instead.
|
||||||
|
|
||||||
|
export type ClientHints = Bowser.ClientHints;
|
||||||
|
export type Parser = Bowser.Parser.Parser;
|
||||||
|
export type ParsedResult = Bowser.Parser.ParsedResult;
|
||||||
|
export type Details = Bowser.Parser.Details;
|
||||||
|
export type BrowserDetails = Bowser.Parser.BrowserDetails;
|
||||||
|
export type EngineDetails = Bowser.Parser.EngineDetails;
|
||||||
|
export type OSDetails = Bowser.Parser.OSDetails;
|
||||||
|
export type PlatformDetails = Bowser.Parser.PlatformDetails;
|
||||||
|
export type checkTree = Bowser.Parser.checkTree;
|
||||||
14506
package-lock.json
generated
14506
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
98
package.json
98
package.json
@@ -2,10 +2,12 @@
|
|||||||
"name": "bowser",
|
"name": "bowser",
|
||||||
"description": "Lightweight browser detector",
|
"description": "Lightweight browser detector",
|
||||||
"files": [
|
"files": [
|
||||||
"src/",
|
"src",
|
||||||
"es5.js",
|
"es5.js",
|
||||||
"bundled.js",
|
"bundled.js",
|
||||||
"index.d.ts"
|
"bowser.mjs",
|
||||||
|
"index.d.ts",
|
||||||
|
"index.d.mts"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"browser",
|
"browser",
|
||||||
@@ -33,41 +35,85 @@
|
|||||||
"browser": "es5.js",
|
"browser": "es5.js",
|
||||||
"module": "src/bowser.js",
|
"module": "src/bowser.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"types": "./index.d.mts",
|
||||||
|
"default": "./bowser.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"default": "./es5.js"
|
||||||
|
},
|
||||||
|
"default": "./es5.js"
|
||||||
|
},
|
||||||
|
"./es5.js": "./es5.js",
|
||||||
|
"./es5": "./es5.js",
|
||||||
|
"./bundled.js": "./bundled.js",
|
||||||
|
"./bundled": "./bundled.js",
|
||||||
|
"./bowser.mjs": {
|
||||||
|
"types": "./index.d.mts",
|
||||||
|
"default": "./bowser.mjs"
|
||||||
|
},
|
||||||
|
"./src/bowser.js": "./src/bowser.js",
|
||||||
|
"./src/bowser": "./src/bowser.js",
|
||||||
|
"./src/constants.js": "./src/constants.js",
|
||||||
|
"./src/constants": "./src/constants.js",
|
||||||
|
"./src/parser.js": "./src/parser.js",
|
||||||
|
"./src/parser": "./src/parser.js",
|
||||||
|
"./src/parser-browsers.js": "./src/parser-browsers.js",
|
||||||
|
"./src/parser-browsers": "./src/parser-browsers.js",
|
||||||
|
"./src/parser-engines.js": "./src/parser-engines.js",
|
||||||
|
"./src/parser-engines": "./src/parser-engines.js",
|
||||||
|
"./src/parser-os.js": "./src/parser-os.js",
|
||||||
|
"./src/parser-os": "./src/parser-os.js",
|
||||||
|
"./src/parser-platforms.js": "./src/parser-platforms.js",
|
||||||
|
"./src/parser-platforms": "./src/parser-platforms.js",
|
||||||
|
"./src/utils.js": "./src/utils.js",
|
||||||
|
"./src/utils": "./src/utils.js",
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/bowser-js/bowser.git"
|
"url": "git+https://github.com/bowser-js/bowser.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.11.6",
|
"@arethetypeswrong/cli": "^0.18.5",
|
||||||
"@babel/core": "^7.8.0",
|
"@babel/cli": "^7.29.7",
|
||||||
"@babel/polyfill": "^7.8.3",
|
"@babel/core": "^7.29.7",
|
||||||
"@babel/preset-env": "^7.8.2",
|
"@babel/preset-env": "^7.29.7",
|
||||||
"@babel/register": "^7.8.3",
|
"@babel/register": "^7.29.7",
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@rolldown/plugin-babel": "0.2.3",
|
||||||
"ava": "^3.0.0",
|
"ava": "^3.0.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-plugin-add-module-exports": "^1.0.4",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-plugin-istanbul": "^8.0.0",
|
||||||
"babel-plugin-add-module-exports": "^1.0.2",
|
"core-js": "^3.49.0",
|
||||||
"babel-plugin-istanbul": "^6.0.0",
|
|
||||||
"compression-webpack-plugin": "^4.0.0",
|
|
||||||
"coveralls": "^3.0.6",
|
"coveralls": "^3.0.6",
|
||||||
"docdash": "^1.1.1",
|
"docdash": "^1.1.1",
|
||||||
"eslint": "^6.5.1",
|
"eslint": "^10.8.0",
|
||||||
"eslint-config-airbnb-base": "^13.2.0",
|
"eslint-config-airbnb-extended": "^3.1.0",
|
||||||
"eslint-plugin-ava": "^10.0.0",
|
"eslint-plugin-ava": "^17.0.1",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"gh-pages": "^3.0.0",
|
"gh-pages": "^3.0.0",
|
||||||
"jsdoc": "^3.6.3",
|
"jsdoc": "^3.6.3",
|
||||||
"nyc": "^15.0.0",
|
"nyc": "^18.0.0",
|
||||||
"sinon": "^9.0.0",
|
"publint": "^0.3.22",
|
||||||
|
"regenerator-runtime": "^0.14.1",
|
||||||
|
"sinon": "^22.0.0",
|
||||||
|
"terser": "^5.49.0",
|
||||||
"testem": "^3.0.0",
|
"testem": "^3.0.0",
|
||||||
"webpack": "^4.41.0",
|
"tsdown": "^0.22.14",
|
||||||
"webpack-bundle-analyzer": "^3.5.2",
|
"typescript": "^5.9.3",
|
||||||
"webpack-cli": "^3.3.9",
|
|
||||||
"yamljs": "^0.3.0"
|
"yamljs": "^0.3.0"
|
||||||
},
|
},
|
||||||
"ava": {
|
"ava": {
|
||||||
"require": [
|
"require": [
|
||||||
"@babel/register"
|
"@babel/register"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"test/**/*.js",
|
||||||
|
"!test/package/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -77,16 +123,18 @@
|
|||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --config webpack.config.js",
|
"build": "tsdown",
|
||||||
"generate-and-deploy-docs": "npm run generate-docs && gh-pages --dist docs --dest docs",
|
"generate-and-deploy-docs": "pnpm generate-docs && gh-pages --dist docs --dest docs",
|
||||||
"watch": "webpack --watch --config webpack.config.js",
|
"watch": "tsdown --watch",
|
||||||
"lint:check": "eslint ./src",
|
"lint:check": "eslint ./src",
|
||||||
"lint:fix": "eslint --fix ./src",
|
"lint:fix": "eslint --fix ./src",
|
||||||
"testem": "testem",
|
"testem": "testem",
|
||||||
"test": "nyc --reporter=html --reporter=text ava",
|
"test": "nyc --reporter=html --reporter=text ava",
|
||||||
"test:watch": "ava --watch",
|
"test:watch": "ava --watch",
|
||||||
|
"test:package": "node test/package/smoke.cjs",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"generate-docs": "jsdoc -c jsdoc.json"
|
"generate-docs": "jsdoc -c jsdoc.json"
|
||||||
},
|
},
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"packageManager": "pnpm@11.18.0"
|
||||||
}
|
}
|
||||||
|
|||||||
9770
pnpm-lock.yaml
generated
Normal file
9770
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
allowBuilds:
|
||||||
|
core-js: false
|
||||||
|
unrs-resolver: false
|
||||||
@@ -91,3 +91,4 @@ class Bowser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Bowser;
|
export default Bowser;
|
||||||
|
export const { parse, getParser } = Bowser;
|
||||||
|
|||||||
@@ -210,7 +210,6 @@ class Parser {
|
|||||||
return this.getBrowser().name || '';
|
return this.getBrowser().name || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get browser's version
|
* Get browser's version
|
||||||
* @return {String} version of browser
|
* @return {String} version of browser
|
||||||
|
|||||||
192
test/package/assertions.cjs
Normal file
192
test/package/assertions.cjs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Runs *inside* a throwaway directory that has the packed bowser tarball
|
||||||
|
* extracted into ./node_modules/bowser, so that bare specifiers resolve the way
|
||||||
|
* they would for a real consumer.
|
||||||
|
*
|
||||||
|
* Deliberately written as ES5-compatible CommonJS: this file has to run on
|
||||||
|
* every Node version bowser claims to support, down to 12.16.3. No optional
|
||||||
|
* chaining, no nullish coalescing, no top-level await.
|
||||||
|
*/
|
||||||
|
var assert = require('assert');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var vm = require('vm');
|
||||||
|
|
||||||
|
var UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 '
|
||||||
|
+ '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
|
var passed = 0;
|
||||||
|
function check(name, fn) {
|
||||||
|
fn();
|
||||||
|
passed += 1;
|
||||||
|
console.log(' ok ' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Node version gate — ESM was unflagged in 12.17.0. */
|
||||||
|
function nodeAtLeast(major, minor) {
|
||||||
|
var parts = process.versions.node.split('.');
|
||||||
|
var m = Number(parts[0]);
|
||||||
|
var n = Number(parts[1]);
|
||||||
|
return m > major || (m === major && n >= minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('bowser package smoke test on Node ' + process.versions.node);
|
||||||
|
|
||||||
|
// --- The CommonJS contract -------------------------------------------------
|
||||||
|
// `require('bowser')` must be the Bowser class itself. If it ever becomes
|
||||||
|
// `{ default, parse, getParser }`, every existing consumer breaks.
|
||||||
|
|
||||||
|
check('require("bowser") is the class, not a module namespace', function () {
|
||||||
|
var Bowser = require('bowser');
|
||||||
|
assert.strictEqual(typeof Bowser, 'function');
|
||||||
|
assert.strictEqual(typeof Bowser.getParser, 'function');
|
||||||
|
assert.strictEqual(typeof Bowser.parse, 'function');
|
||||||
|
assert.strictEqual(
|
||||||
|
Bowser.default,
|
||||||
|
undefined,
|
||||||
|
'require("bowser").default is set — the CJS interop unwrap was lost',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
check('require("bowser") parses a user agent', function () {
|
||||||
|
var Bowser = require('bowser');
|
||||||
|
assert.strictEqual(Bowser.parse(UA).browser.name, 'Chrome');
|
||||||
|
assert.strictEqual(Bowser.getParser(UA).getBrowserName(), 'Chrome');
|
||||||
|
});
|
||||||
|
|
||||||
|
check('constant maps are exposed', function () {
|
||||||
|
var Bowser = require('bowser');
|
||||||
|
assert.strictEqual(typeof Bowser.BROWSER_MAP, 'object');
|
||||||
|
assert.strictEqual(typeof Bowser.ENGINE_MAP, 'object');
|
||||||
|
assert.strictEqual(typeof Bowser.OS_MAP, 'object');
|
||||||
|
assert.strictEqual(typeof Bowser.PLATFORMS_MAP, 'object');
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Legacy deep paths -----------------------------------------------------
|
||||||
|
// These resolved before the exports map existed. Adding an exports map without
|
||||||
|
// listing them turns them into ERR_PACKAGE_PATH_NOT_EXPORTED.
|
||||||
|
|
||||||
|
['bowser/es5.js', 'bowser/es5', 'bowser/bundled.js', 'bowser/bundled'].forEach(function (id) {
|
||||||
|
check('require("' + id + '") works', function () {
|
||||||
|
var B = require(id);
|
||||||
|
assert.strictEqual(typeof B, 'function');
|
||||||
|
assert.strictEqual(B.parse(UA).browser.name, 'Chrome');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The src/*.js files are ES module sources, so they resolve but do not execute
|
||||||
|
// under require(). Bundlers are the real consumer here. Assert resolution only.
|
||||||
|
[
|
||||||
|
'bowser.js', 'constants.js', 'parser.js', 'parser-browsers.js',
|
||||||
|
'parser-engines.js', 'parser-os.js', 'parser-platforms.js', 'utils.js',
|
||||||
|
].forEach(function (file) {
|
||||||
|
var withExt = 'bowser/src/' + file;
|
||||||
|
var withoutExt = withExt.replace(/\.js$/, '');
|
||||||
|
check('resolves "' + withExt + '" and "' + withoutExt + '"', function () {
|
||||||
|
assert.ok(fs.existsSync(require.resolve(withExt)));
|
||||||
|
assert.ok(fs.existsSync(require.resolve(withoutExt)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
check('require.resolve("bowser/package.json") works', function () {
|
||||||
|
assert.ok(fs.existsSync(require.resolve('bowser/package.json')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- The published manifest ------------------------------------------------
|
||||||
|
|
||||||
|
check('main/browser/module/types fields are unchanged', function () {
|
||||||
|
var pkg = require('bowser/package.json');
|
||||||
|
assert.strictEqual(pkg.main, 'es5.js');
|
||||||
|
assert.strictEqual(pkg.browser, 'es5.js');
|
||||||
|
assert.strictEqual(pkg.module, 'src/bowser.js');
|
||||||
|
assert.strictEqual(pkg.types, 'index.d.ts');
|
||||||
|
});
|
||||||
|
|
||||||
|
check('no "engines" field (would warn/fail installs on old Node)', function () {
|
||||||
|
var pkg = require('bowser/package.json');
|
||||||
|
assert.strictEqual(pkg.engines, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
check('no "type" field (would reclassify es5.js as ESM)', function () {
|
||||||
|
var pkg = require('bowser/package.json');
|
||||||
|
assert.strictEqual(pkg.type, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
check('published file list is exactly what we expect', function () {
|
||||||
|
var root = path.dirname(require.resolve('bowser/package.json'));
|
||||||
|
var actual = [];
|
||||||
|
(function walk(dir, prefix) {
|
||||||
|
fs.readdirSync(dir).forEach(function (name) {
|
||||||
|
var full = path.join(dir, name);
|
||||||
|
if (fs.statSync(full).isDirectory()) walk(full, prefix + name + '/');
|
||||||
|
else actual.push(prefix + name);
|
||||||
|
});
|
||||||
|
}(root, ''));
|
||||||
|
|
||||||
|
var expected = [
|
||||||
|
'LICENSE', 'README.md', 'bowser.mjs', 'bundled.js', 'es5.js',
|
||||||
|
'index.d.mts', 'index.d.ts', 'package.json',
|
||||||
|
'src/bowser.js', 'src/constants.js', 'src/parser-browsers.js',
|
||||||
|
'src/parser-engines.js', 'src/parser-os.js', 'src/parser-platforms.js',
|
||||||
|
'src/parser.js', 'src/utils.js',
|
||||||
|
];
|
||||||
|
assert.deepStrictEqual(actual.sort(), expected.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- The UMD / CDN contract ------------------------------------------------
|
||||||
|
// Script-tag consumers get `window.bowser` (lowercase). Nothing else covers
|
||||||
|
// this path, and a renamed global fails silently at runtime.
|
||||||
|
|
||||||
|
check('es5.js sets a lowercase `bowser` global when loaded as a script', function () {
|
||||||
|
var file = require.resolve('bowser/es5.js');
|
||||||
|
var sandbox = {};
|
||||||
|
sandbox.self = sandbox;
|
||||||
|
vm.runInNewContext(fs.readFileSync(file, 'utf8'), sandbox);
|
||||||
|
assert.strictEqual(typeof sandbox.bowser, 'function', 'global `bowser` not set');
|
||||||
|
assert.strictEqual(sandbox.bowser.parse(UA).browser.name, 'Chrome');
|
||||||
|
assert.strictEqual(sandbox.Bowser, undefined, 'unexpected capitalised global');
|
||||||
|
});
|
||||||
|
|
||||||
|
check('es5.js contains no ES6 template literals', function () {
|
||||||
|
// rolldown's built-in (oxc) minifier rewrites every string literal as a
|
||||||
|
// template literal, which is a syntax error in the old browsers this bundle
|
||||||
|
// targets. Guards against the terser step being dropped from the build.
|
||||||
|
var code = fs.readFileSync(require.resolve('bowser/es5.js'), 'utf8');
|
||||||
|
assert.strictEqual(code.indexOf('`'), -1, 'es5.js contains a backtick');
|
||||||
|
});
|
||||||
|
|
||||||
|
check('es5.js keeps the copyright banner', function () {
|
||||||
|
var code = fs.readFileSync(require.resolve('bowser/es5.js'), 'utf8');
|
||||||
|
assert.ok(code.indexOf('Bowser - a browser detector') !== -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- The ESM contract ------------------------------------------------------
|
||||||
|
|
||||||
|
if (!nodeAtLeast(12, 17)) {
|
||||||
|
console.log(' -- skipping ESM checks (Node ' + process.versions.node + ' < 12.17)');
|
||||||
|
console.log('\n' + passed + ' checks passed');
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
eval('import("bowser")')
|
||||||
|
.then(function (mod) {
|
||||||
|
check('import("bowser") has a working default export', function () {
|
||||||
|
assert.strictEqual(typeof mod.default, 'function');
|
||||||
|
assert.strictEqual(mod.default.parse(UA).browser.name, 'Chrome');
|
||||||
|
});
|
||||||
|
// This is issue #511: named imports must work, and must work unbound.
|
||||||
|
check('import("bowser") has working named exports', function () {
|
||||||
|
assert.strictEqual(typeof mod.parse, 'function');
|
||||||
|
assert.strictEqual(typeof mod.getParser, 'function');
|
||||||
|
var parse = mod.parse;
|
||||||
|
var getParser = mod.getParser;
|
||||||
|
assert.strictEqual(parse(UA).browser.name, 'Chrome');
|
||||||
|
assert.strictEqual(getParser(UA).getBrowserName(), 'Chrome');
|
||||||
|
});
|
||||||
|
console.log('\n' + passed + ' checks passed');
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error('\nESM check failed: ' + err.stack);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
100
test/package/smoke.cjs
Normal file
100
test/package/smoke.cjs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* Verifies the *packed* bowser package against the surface real consumers use.
|
||||||
|
*
|
||||||
|
* node test/package/smoke.cjs <path-to-bowser-x.y.z.tgz>
|
||||||
|
*
|
||||||
|
* The tarball is extracted into a throwaway directory as ./node_modules/bowser
|
||||||
|
* and `assertions.cjs` is run next to it, so bare specifiers such as
|
||||||
|
* `require('bowser')` and `require('bowser/es5.js')` resolve exactly as they
|
||||||
|
* would after `npm install bowser`.
|
||||||
|
*
|
||||||
|
* Run this on every Node version bowser supports — that is the point of it.
|
||||||
|
* See `.github/workflows/pull-request.yml`.
|
||||||
|
*
|
||||||
|
* Deliberately ES5-compatible CommonJS so it runs on Node 12.16.3.
|
||||||
|
*/
|
||||||
|
var cp = require('child_process');
|
||||||
|
var fs = require('fs');
|
||||||
|
var os = require('os');
|
||||||
|
var path = require('path');
|
||||||
|
var zlib = require('zlib');
|
||||||
|
|
||||||
|
var tarball = process.argv[2];
|
||||||
|
|
||||||
|
if (!tarball) {
|
||||||
|
console.error('usage: node test/package/smoke.cjs <path-to-tarball.tgz>');
|
||||||
|
console.error('');
|
||||||
|
console.error('Create one with:');
|
||||||
|
console.error(' npm version 0.0.0-ci --no-git-tag-version --allow-same-version');
|
||||||
|
console.error(' npm pack');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
tarball = path.resolve(tarball);
|
||||||
|
if (!fs.existsSync(tarball)) {
|
||||||
|
console.error('no such tarball: ' + tarball);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bowser-smoke-'));
|
||||||
|
var modules = path.join(tmp, 'node_modules');
|
||||||
|
fs.mkdirSync(modules);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts an npm tarball using only Node built-ins, so the test needs no
|
||||||
|
* external `tar`/`npm` process and runs identically on every OS and every
|
||||||
|
* Node version in the support matrix.
|
||||||
|
*
|
||||||
|
* npm tarballs are gzipped ustar archives of regular files (npm rejects
|
||||||
|
* anything else at publish time), which is the only shape handled here:
|
||||||
|
* pax/global extended headers and directory entries are skipped, and parent
|
||||||
|
* directories are created per file instead.
|
||||||
|
*/
|
||||||
|
function extractNpmTarball(tgz, dest) {
|
||||||
|
var buf = zlib.gunzipSync(fs.readFileSync(tgz));
|
||||||
|
var offset = 0;
|
||||||
|
while (offset + 512 <= buf.length) {
|
||||||
|
var header = buf.slice(offset, offset + 512);
|
||||||
|
offset += 512;
|
||||||
|
if (header[0] === 0) break; // zero block: end of archive
|
||||||
|
var name = header.slice(0, 100).toString('utf8').replace(/\0[\s\S]*$/, '');
|
||||||
|
var prefix = header.slice(345, 500).toString('utf8').replace(/\0[\s\S]*$/, '');
|
||||||
|
if (prefix) name = prefix + '/' + name;
|
||||||
|
var size = parseInt(header.slice(124, 136).toString('utf8'), 8) || 0;
|
||||||
|
var type = header[156];
|
||||||
|
if (type === 48 /* '0' */ || type === 0) {
|
||||||
|
var target = path.join(dest, name);
|
||||||
|
if (target.indexOf(dest + path.sep) !== 0) {
|
||||||
|
throw new Error('tarball entry escapes destination: ' + name);
|
||||||
|
}
|
||||||
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||||
|
fs.writeFileSync(target, buf.slice(offset, offset + size));
|
||||||
|
}
|
||||||
|
offset += Math.ceil(size / 512) * 512;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// npm tarballs always unpack to a single top-level `package/` directory.
|
||||||
|
extractNpmTarball(tarball, modules);
|
||||||
|
fs.renameSync(path.join(modules, 'package'), path.join(modules, 'bowser'));
|
||||||
|
|
||||||
|
var runner = path.join(tmp, 'assertions.cjs');
|
||||||
|
fs.writeFileSync(runner, fs.readFileSync(path.join(__dirname, 'assertions.cjs')));
|
||||||
|
|
||||||
|
var result = cp.spawnSync(process.execPath, [runner], { cwd: tmp, stdio: 'inherit' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// fs.rmSync landed in Node 14.14; recursive fs.rmdirSync (12.10+) covers the
|
||||||
|
// bottom of the matrix. Both work on Windows, unlike shelling out to `rm`.
|
||||||
|
if (fs.rmSync) {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
} else {
|
||||||
|
fs.rmdirSync(tmp, { recursive: true });
|
||||||
|
}
|
||||||
|
} catch (e) { /* best effort */ }
|
||||||
|
|
||||||
|
if (result.status !== 0) {
|
||||||
|
console.error('\npackage smoke test FAILED on Node ' + process.versions.node);
|
||||||
|
process.exit(result.status || 1);
|
||||||
|
}
|
||||||
107
tsdown.config.ts
Normal file
107
tsdown.config.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { defineConfig } from 'tsdown';
|
||||||
|
import babel from '@rolldown/plugin-babel';
|
||||||
|
import { minify } from 'terser';
|
||||||
|
|
||||||
|
const banner = `/*!
|
||||||
|
* Bowser - a browser detector
|
||||||
|
* https://github.com/lancedikson/bowser
|
||||||
|
* MIT License | (c) Dustin Diaz 2012-2015
|
||||||
|
* MIT License | (c) Denis Demchenko 2015-2026
|
||||||
|
*/`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The browser targets the UMD bundles are transpiled down to. Unchanged from
|
||||||
|
* the webpack build these bundles replaced — the published `es5.js` is relied
|
||||||
|
* on by script-tag and CDN consumers on very old browsers.
|
||||||
|
*/
|
||||||
|
const legacyTargets = {
|
||||||
|
ie: '8',
|
||||||
|
browsers: '>2%',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `useBuiltIns: false` for `es5.js` (syntax transpilation only) and `'entry'`
|
||||||
|
* for `bundled.js`, which expands the `core-js/stable` import in its entry.
|
||||||
|
*/
|
||||||
|
const legacyBabel = (useBuiltIns: false | 'entry') => babel({
|
||||||
|
presets: [['@babel/preset-env', {
|
||||||
|
// Let rolldown emit the UMD wrapper; babel only lowers syntax here.
|
||||||
|
modules: false,
|
||||||
|
loose: true,
|
||||||
|
useBuiltIns,
|
||||||
|
...(useBuiltIns ? { corejs: '3' } : {}),
|
||||||
|
targets: legacyTargets,
|
||||||
|
}]],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minifies the UMD chunks with terser instead of rolldown's built-in (oxc)
|
||||||
|
* minifier.
|
||||||
|
*
|
||||||
|
* This is not a preference. oxc's minifier prints every string literal as a
|
||||||
|
* template literal and refuses any `compress.target` below `es2015`, so it
|
||||||
|
* cannot emit ES5 — it would silently undo babel's lowering and break the very
|
||||||
|
* old browsers `es5.js` exists to serve. terser (`ecma: 5`) is also what the
|
||||||
|
* webpack 4 build this replaced used, via terser-webpack-plugin.
|
||||||
|
*/
|
||||||
|
const terser = () => ({
|
||||||
|
name: 'bowser:terser',
|
||||||
|
async renderChunk(code: string, chunk: { fileName: string }) {
|
||||||
|
const result = await minify(code, {
|
||||||
|
ecma: 5,
|
||||||
|
// IE 8: reserved words as property names must stay quoted.
|
||||||
|
ie8: true,
|
||||||
|
safari10: true,
|
||||||
|
format: {
|
||||||
|
// Keep the `/*!` banner.
|
||||||
|
comments: /^!/,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (typeof result.code !== 'string') {
|
||||||
|
throw new Error(`terser produced no output for ${chunk.fileName}`);
|
||||||
|
}
|
||||||
|
return { code: result.code };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const umd = (name: string, entry: string, useBuiltIns: false | 'entry') => ({
|
||||||
|
entry: { [name]: entry },
|
||||||
|
format: ['umd' as const],
|
||||||
|
// Lowercase, matching the global the webpack build published. Renaming this
|
||||||
|
// to `Bowser` would break every `<script src=".../bowser/es5.js">` consumer.
|
||||||
|
globalName: 'bowser',
|
||||||
|
outputOptions: {
|
||||||
|
// `module.exports = Bowser` rather than `{ default: Bowser }`.
|
||||||
|
exports: 'default' as const,
|
||||||
|
// Default would be `<name>.umd.js`; these files are published paths.
|
||||||
|
entryFileNames: '[name].js',
|
||||||
|
},
|
||||||
|
outDir: '.',
|
||||||
|
platform: 'browser' as const,
|
||||||
|
plugins: [legacyBabel(useBuiltIns), terser()],
|
||||||
|
// webpack ran in `mode: 'production'`; minification happens in `terser()`
|
||||||
|
// above, so rolldown's own minifier stays off. See its comment for why.
|
||||||
|
minify: false,
|
||||||
|
banner,
|
||||||
|
dts: false,
|
||||||
|
// outDir is the repo root — never let tsdown clean it.
|
||||||
|
clean: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
umd('es5', 'build/entries/es5.js', false),
|
||||||
|
umd('bundled', 'build/entries/bundled.js', 'entry'),
|
||||||
|
{
|
||||||
|
// Modern ESM build. Reached via the `import` condition of the exports map.
|
||||||
|
// Deliberately not run through babel: `module` still points at the raw
|
||||||
|
// `src/bowser.js`, so this must not be *more* conservative than that.
|
||||||
|
entry: { bowser: 'src/bowser.js' },
|
||||||
|
format: ['esm'],
|
||||||
|
outDir: '.',
|
||||||
|
outExtensions: () => ({ js: '.mjs' }),
|
||||||
|
platform: 'browser',
|
||||||
|
banner,
|
||||||
|
dts: false,
|
||||||
|
clean: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
const banner = `Bowser - a browser detector
|
|
||||||
https://github.com/lancedikson/bowser
|
|
||||||
MIT License | (c) Dustin Diaz 2012-2015
|
|
||||||
MIT License | (c) Denis Demchenko 2015-2026`;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
plugins: [
|
|
||||||
new CompressionPlugin(),
|
|
||||||
new webpack.BannerPlugin({ banner }),
|
|
||||||
],
|
|
||||||
mode: 'production', // "production" | "development" | "none"
|
|
||||||
// Chosen mode tells webpack to use its built-in optimizations accordingly.
|
|
||||||
entry: {
|
|
||||||
bundled: ['@babel/polyfill', './src/bowser.js'],
|
|
||||||
es5: './src/bowser.js',
|
|
||||||
}, // string | object | array
|
|
||||||
// defaults to ./src
|
|
||||||
// Here the application starts executing
|
|
||||||
// and webpack starts bundling
|
|
||||||
output: {
|
|
||||||
// options related to how webpack emits results
|
|
||||||
path: path.resolve(__dirname, './'), // string
|
|
||||||
// the target directory for all output files
|
|
||||||
// must be an absolute path (use the Node.js path module)
|
|
||||||
filename: '[name].js', // string
|
|
||||||
// the filename template for entry chunks
|
|
||||||
library: 'bowser',
|
|
||||||
libraryTarget: 'umd', // universal module definition
|
|
||||||
// the type of the exported library
|
|
||||||
globalObject: 'this',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
// configuration regarding modules
|
|
||||||
rules: [
|
|
||||||
// rules for modules (configure loaders, parser options, etc.)
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /(node_modules|bower_components)/,
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user