react-threat-map 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DECISIONS.md +149 -18
- package/dist/index.cjs +6 -2
- package/dist/index.js +6 -2
- package/package.json +26 -7
package/DECISIONS.md
CHANGED
|
@@ -247,24 +247,8 @@ Every runtime dependency, and why it earns its place:
|
|
|
247
247
|
Both are declared `external`, so consumers dedupe them against their own copies of d3
|
|
248
248
|
rather than shipping a second one.
|
|
249
249
|
|
|
250
|
-
`react` is a **peer** dependency
|
|
251
|
-
|
|
252
|
-
The floor is 16.14.0, not 16.8.0 (hooks) as one might expect: the compiled output uses
|
|
253
|
-
the automatic JSX runtime, so it imports `react/jsx-runtime`, and that entry point first
|
|
254
|
-
ships in **16.14.0**. On 16.8–16.13 the package installs and then fails at bundle time on
|
|
255
|
-
an unresolved module, so those versions must stay outside the range.
|
|
256
|
-
|
|
257
|
-
Nothing in the library needs more than that floor — it uses only `useState`, `useEffect`,
|
|
258
|
-
`useRef`, `useMemo`, and `useCallback`. The range was originally `>=18` for no recorded
|
|
259
|
-
reason, which excluded working consumers on React 16.14/17; widened in 0.2.1 after
|
|
260
|
-
verifying typecheck and a mount/re-render/unmount render pass on 16.14, 17, 18, and 19.
|
|
261
|
-
|
|
262
|
-
Supporting that range is also why the public types import `CSSProperties`, `MouseEvent`,
|
|
263
|
-
`ReactElement`, and `RefObject` **by name** from `react` rather than reaching through the
|
|
264
|
-
`React` UMD global or the global `JSX` namespace. A bare `JSX.Element` in an emitted `.d.ts`
|
|
265
|
-
resolves against whatever `@types/react` the consumer has — and `@types/react@19` removed
|
|
266
|
-
the global `JSX` namespace, so it broke React 19 consumers compiling with
|
|
267
|
-
`skipLibCheck: false`. Named type imports are identical across `@types/react` 16 to 19.
|
|
250
|
+
`react` is a **peer** dependency, never bundled. The supported range and the reasoning
|
|
251
|
+
behind its floor are in [§7](#7-react-version-support-1614-and-up).
|
|
268
252
|
|
|
269
253
|
Deliberately **not** dependencies: no state manager, no styling library, no CSS-in-JS, no
|
|
270
254
|
animation library, no `d3-selection`/`d3-zoom`/`d3-scale`. The library ships zero CSS —
|
|
@@ -346,3 +330,150 @@ lines — silently erase every self-directed threat on the map.
|
|
|
346
330
|
Consumers upgrading from 0.1.x see new animated loops wherever their feed contains
|
|
347
331
|
same-region attacks that previously rendered as static dots. This is why the change went
|
|
348
332
|
out as a minor bump rather than a patch.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 7. React version support: 16.14 and up
|
|
337
|
+
|
|
338
|
+
**Chosen:** `peerDependencies: { react: ">=16.14.0" }`, covering React 16.14, 17, 18, and 19.
|
|
339
|
+
|
|
340
|
+
### Why the floor is 16.14.0 and not 16.8.0
|
|
341
|
+
|
|
342
|
+
16.8.0 is the obvious guess, since that is where hooks landed and hooks are all this library
|
|
343
|
+
uses. It is wrong. The compiled output uses the **automatic JSX runtime**, so it imports
|
|
344
|
+
`react/jsx-runtime`, and that entry point first ships in **16.14.0**.
|
|
345
|
+
|
|
346
|
+
The failure mode is what makes this worth recording: on 16.8–16.13 the package *installs
|
|
347
|
+
cleanly* and then fails at the consumer's bundler on an unresolved module. A range of
|
|
348
|
+
`>=16.8.0` would be a promise that npm accepts and webpack rejects, which is the worst
|
|
349
|
+
place for the error to surface. If the JSX transform is ever changed back to `classic`, the
|
|
350
|
+
floor can drop to 16.8.0 — those two facts move together.
|
|
351
|
+
|
|
352
|
+
### Why not the original `>=18.0.0`
|
|
353
|
+
|
|
354
|
+
There was no recorded reason for it, and it was wrong in both directions: it excluded React
|
|
355
|
+
16.14/17 consumers the library works fine on, and it *included* React 19, which was broken
|
|
356
|
+
(below). Nothing here needs more than the floor — the library uses only `useState`,
|
|
357
|
+
`useEffect`, `useRef`, `useMemo`, and `useCallback`.
|
|
358
|
+
|
|
359
|
+
Widened in 0.2.1, as a patch: broadening a peer range cannot break an existing consumer.
|
|
360
|
+
|
|
361
|
+
### Why the public types import from `react` by name
|
|
362
|
+
|
|
363
|
+
Supporting a wide range is why `CSSProperties`, `MouseEvent`, `ReactElement`, and
|
|
364
|
+
`RefObject` are imported **by name** rather than reached through the `React` UMD global or
|
|
365
|
+
the global `JSX` namespace.
|
|
366
|
+
|
|
367
|
+
A bare `JSX.Element` in an emitted `.d.ts` resolves against whatever `@types/react` the
|
|
368
|
+
consumer happens to have, which is not a thing this library controls. `@types/react@19`
|
|
369
|
+
removed the global `JSX` namespace, so the 0.2.0 type surface failed for React 19 consumers
|
|
370
|
+
compiling with `skipLibCheck: false`:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
dist/index.d.ts(40,76): error TS2503: Cannot find namespace 'JSX'.
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Those four names are exported identically by `@types/react` 16 through 19. The rule is
|
|
377
|
+
enforced by `tests/react-compat.test.ts`, which rejects `React.*` and `JSX.*` in `src/`.
|
|
378
|
+
|
|
379
|
+
### Tradeoff accepted
|
|
380
|
+
|
|
381
|
+
**The test suite does not run against the floor.** `@testing-library/react@16` requires
|
|
382
|
+
React 18+, so every test here executes on 18. Covering 16.14 properly means a second
|
|
383
|
+
devDependency tree and a CI matrix, which was judged not worth it for a library using five
|
|
384
|
+
hooks. A green suite is therefore *not* evidence that the floor works.
|
|
385
|
+
|
|
386
|
+
What closes most of the gap is static: `tests/react-compat.test.ts` asserts that every name
|
|
387
|
+
imported from `react` appears on an allowlist checked against 16.14 by hand, so reaching
|
|
388
|
+
for `useSyncExternalStore`, `useId`, `useTransition`, `useDeferredValue`, or `use` fails the
|
|
389
|
+
build rather than shipping. It cannot catch behavioural differences — only API existence.
|
|
390
|
+
|
|
391
|
+
The one behavioural difference worth knowing: React 16/17 do not batch `setState` outside
|
|
392
|
+
event handlers, so the `ResizeObserver`, `matchMedia`, and geo-load callbacks each trigger
|
|
393
|
+
their own render there. Every one of those call sites issues a single `setState`, so the
|
|
394
|
+
practical cost is nil — but a future hook that sets two pieces of state in one async
|
|
395
|
+
callback would render twice on the floor and once on 18+.
|
|
396
|
+
|
|
397
|
+
### Known behavioural differences on the floor
|
|
398
|
+
|
|
399
|
+
Each of these is invisible to a suite running on 18, so each carries a guard that fails on
|
|
400
|
+
any version. Append to this list rather than fixing quietly — the entries are the only
|
|
401
|
+
record of what the static allowlist cannot reach.
|
|
402
|
+
|
|
403
|
+
**1. Numeric style values.** React appends `px` to numeric `style` values unless the property
|
|
404
|
+
is on its unitless allowlist, and `aspectRatio` only joined that list in React 18. The
|
|
405
|
+
wrapper's fallback ratio was emitted as a number, so on 16.14/17 it serialized to
|
|
406
|
+
`aspect-ratio: 2px` — invalid, dropped by the browser, wrapper collapsed to zero height,
|
|
407
|
+
absolutely-positioned canvases gated off, **map entirely invisible**. It affected every
|
|
408
|
+
projection, not just custom ones, and the whole no-explicit-`height` default path.
|
|
409
|
+
|
|
410
|
+
Found by a consumer on React 16.14, not by CI — precisely the gap this section predicted.
|
|
411
|
+
Fixed by `aspectRatioStyleFor` in `src/render/projection.ts`, which stringifies deliberately;
|
|
412
|
+
guarded by `tests/render/projection.test.ts`, which asserts the emitted **type** rather than
|
|
413
|
+
the rendered value, since the rendered value is correct on 18 either way.
|
|
414
|
+
|
|
415
|
+
The general rule this implies: **any numeric value handed to `style` must be checked against
|
|
416
|
+
React 17's unitless allowlist, or stringified.** `lineHeight` and `flexGrow` are on that list
|
|
417
|
+
and safe; `scale`, `translate`, `rotate` and `inset` are not.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 8. Package entry points: per-condition types, plus a `typesVersions` fallback
|
|
422
|
+
|
|
423
|
+
**Chosen:** `exports` declares types *inside* each of the `import` and `require`
|
|
424
|
+
conditions, and `typesVersions` maps the `geo` subpath for resolvers that ignore `exports`
|
|
425
|
+
entirely.
|
|
426
|
+
|
|
427
|
+
This is not decoration. 0.2.1 shipped with two independent faults here, both invisible to
|
|
428
|
+
every other check in the repo: the suite imports from `src/`, and the smoke test exercises
|
|
429
|
+
one resolution mode. A package can be entirely correct at runtime and still fail to
|
|
430
|
+
typecheck for a large share of its consumers.
|
|
431
|
+
|
|
432
|
+
### `moduleResolution: "node"` ignores `exports`, so the subpath had no types
|
|
433
|
+
|
|
434
|
+
Under node10 resolution — still the default for any project that has not opted in, which
|
|
435
|
+
is disproportionately the React 16/17 projects the floor in §7 deliberately invites —
|
|
436
|
+
`react-threat-map/geo` resolved to nothing:
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
error TS2307: Cannot find module 'react-threat-map/geo' or its corresponding type
|
|
440
|
+
declarations. There are types at '.../dist/geo.d.ts', but this result could not be
|
|
441
|
+
resolved under your current 'moduleResolution' setting.
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`typesVersions` is the only mechanism node10 honours for subpath types, so it is the fix.
|
|
445
|
+
|
|
446
|
+
The workaround a consumer reaches for first is a trap worth naming, because it *looks*
|
|
447
|
+
like it works: `react-threat-map/dist/geo` typechecks fine, then fails at runtime with
|
|
448
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`, since the deep path is not in `exports`. Webpack 5
|
|
449
|
+
enforces the same restriction. Neither specifier satisfied both the compiler and the
|
|
450
|
+
bundler — which is why this needed fixing in the library rather than working around
|
|
451
|
+
downstream.
|
|
452
|
+
|
|
453
|
+
### One `types` per subpath made the package masquerade as ESM
|
|
454
|
+
|
|
455
|
+
The map previously declared a single `"types": "./dist/index.d.ts"` covering both
|
|
456
|
+
conditions. Because this package is `"type": "module"`, that `.d.ts` is read as an ES
|
|
457
|
+
module — so a **CommonJS** consumer on `node16`/`nodenext` was told the types were ESM
|
|
458
|
+
while the runtime correctly handed it `dist/index.cjs`:
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
error TS1479: The current file is a CommonJS module whose imports will produce 'require'
|
|
462
|
+
calls; however, the referenced file is an ECMAScript module...
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
tsup was *already emitting* `dist/index.d.cts` and `dist/geo.d.cts`. The exports map simply
|
|
466
|
+
never pointed at them, so they shipped in every tarball as dead weight. Declaring types
|
|
467
|
+
per-condition is what makes the dual build actually dual.
|
|
468
|
+
|
|
469
|
+
### Tradeoff accepted
|
|
470
|
+
|
|
471
|
+
`typesVersions` is a legacy mechanism superseded by `exports`, and it duplicates the `geo`
|
|
472
|
+
mapping in a second place. Adding a subpath therefore means editing both, and forgetting
|
|
473
|
+
one breaks only node10 consumers — the group least likely to be on the maintainer's
|
|
474
|
+
machine. It is kept because node10 offers no alternative, and dropping it would mean
|
|
475
|
+
telling React 16/17 consumers to change their `tsconfig` to install a library that
|
|
476
|
+
otherwise supports them.
|
|
477
|
+
|
|
478
|
+
`npm run check:exports` (arethetypeswrong) runs in CI and covers every entry point against
|
|
479
|
+
every resolution mode. It is what would have caught both faults before 0.2.1 went out.
|
package/dist/index.cjs
CHANGED
|
@@ -345,6 +345,9 @@ function defaultHeightFor(spec, width) {
|
|
|
345
345
|
function aspectRatioFor(spec) {
|
|
346
346
|
return 1 / ratioFor(spec);
|
|
347
347
|
}
|
|
348
|
+
function aspectRatioStyleFor(spec) {
|
|
349
|
+
return String(aspectRatioFor(spec));
|
|
350
|
+
}
|
|
348
351
|
var SPHERE = { type: "Sphere" };
|
|
349
352
|
function createProjection(spec, width, height) {
|
|
350
353
|
const projection = typeof spec === "string" ? (factories[spec] ?? factories.naturalEarth1)() : spec;
|
|
@@ -1326,8 +1329,9 @@ function ThreatMap(props) {
|
|
|
1326
1329
|
// which gates off the canvases, which keeps it collapsed. `aspect-ratio`
|
|
1327
1330
|
// is inert the moment anything else determines the height — a CSS class,
|
|
1328
1331
|
// a flex parent, or the consumer's own `style` below — so it sets a
|
|
1329
|
-
// floor without taking the decision away from them.
|
|
1330
|
-
|
|
1332
|
+
// floor without taking the decision away from them. The value is a string
|
|
1333
|
+
// for React 16.14/17's sake; see aspectRatioStyleFor.
|
|
1334
|
+
...heightProp !== void 0 ? { height: heightProp } : { aspectRatio: aspectRatioStyleFor(projectionProp) },
|
|
1331
1335
|
...style
|
|
1332
1336
|
},
|
|
1333
1337
|
role: "img",
|
package/dist/index.js
CHANGED
|
@@ -344,6 +344,9 @@ function defaultHeightFor(spec, width) {
|
|
|
344
344
|
function aspectRatioFor(spec) {
|
|
345
345
|
return 1 / ratioFor(spec);
|
|
346
346
|
}
|
|
347
|
+
function aspectRatioStyleFor(spec) {
|
|
348
|
+
return String(aspectRatioFor(spec));
|
|
349
|
+
}
|
|
347
350
|
var SPHERE = { type: "Sphere" };
|
|
348
351
|
function createProjection(spec, width, height) {
|
|
349
352
|
const projection = typeof spec === "string" ? (factories[spec] ?? factories.naturalEarth1)() : spec;
|
|
@@ -1325,8 +1328,9 @@ function ThreatMap(props) {
|
|
|
1325
1328
|
// which gates off the canvases, which keeps it collapsed. `aspect-ratio`
|
|
1326
1329
|
// is inert the moment anything else determines the height — a CSS class,
|
|
1327
1330
|
// a flex parent, or the consumer's own `style` below — so it sets a
|
|
1328
|
-
// floor without taking the decision away from them.
|
|
1329
|
-
|
|
1331
|
+
// floor without taking the decision away from them. The value is a string
|
|
1332
|
+
// for React 16.14/17's sake; see aspectRatioStyleFor.
|
|
1333
|
+
...heightProp !== void 0 ? { height: heightProp } : { aspectRatio: aspectRatioStyleFor(projectionProp) },
|
|
1330
1334
|
...style
|
|
1331
1335
|
},
|
|
1332
1336
|
role: "img",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-threat-map",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A React component that renders animated cyberattack threats on a static world map, with intelligent per-region aggregation and first-class US state support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -29,16 +29,33 @@
|
|
|
29
29
|
"main": "./dist/index.cjs",
|
|
30
30
|
"module": "./dist/index.js",
|
|
31
31
|
"types": "./dist/index.d.ts",
|
|
32
|
+
"typesVersions": {
|
|
33
|
+
"*": {
|
|
34
|
+
"geo": [
|
|
35
|
+
"./dist/geo.d.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
32
39
|
"exports": {
|
|
33
40
|
".": {
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/index.d.cts",
|
|
47
|
+
"default": "./dist/index.cjs"
|
|
48
|
+
}
|
|
37
49
|
},
|
|
38
50
|
"./geo": {
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
"import": {
|
|
52
|
+
"types": "./dist/geo.d.ts",
|
|
53
|
+
"default": "./dist/geo.js"
|
|
54
|
+
},
|
|
55
|
+
"require": {
|
|
56
|
+
"types": "./dist/geo.d.cts",
|
|
57
|
+
"default": "./dist/geo.cjs"
|
|
58
|
+
}
|
|
42
59
|
},
|
|
43
60
|
"./package.json": "./package.json"
|
|
44
61
|
},
|
|
@@ -56,6 +73,7 @@
|
|
|
56
73
|
"build": "npm run geo && tsup",
|
|
57
74
|
"dev": "tsup --watch",
|
|
58
75
|
"typecheck": "tsc --noEmit",
|
|
76
|
+
"check:exports": "attw --pack .",
|
|
59
77
|
"test": "vitest run",
|
|
60
78
|
"test:watch": "vitest",
|
|
61
79
|
"test:coverage": "vitest run --coverage",
|
|
@@ -71,6 +89,7 @@
|
|
|
71
89
|
"topojson-client": "^3.1.0"
|
|
72
90
|
},
|
|
73
91
|
"devDependencies": {
|
|
92
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
74
93
|
"@testing-library/jest-dom": "^6.9.1",
|
|
75
94
|
"@testing-library/react": "^16.1.0",
|
|
76
95
|
"@types/d3-geo": "^3.1.0",
|