Skip to content

Commit 8c66b2d

Browse files
authored
Merge pull request #20 from arethetypeswrong/web-improvements
Implement links to results
2 parents ff5e56a + aecc557 commit 8c66b2d

File tree

9 files changed

+86
-22
lines changed

9 files changed

+86
-22
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ This project attempts to analyze npm package contents for issues with their Type
3535
The first things on my roadmap:
3636

3737
- More thorough explanations of problems
38-
- Direct links to results
3938
- Support for DefinitelyTyped analysis
4039
- Official TypeScript module documentation to link to
4140

package-lock.json

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"license": "MIT",
1717
"dependencies": {
1818
"@arethetypeswrong/core": "file:../core",
19+
"immer": "^9.0.21",
1920
"validate-npm-package-name": "^5.0.0"
2021
},
2122
"devDependencies": {

packages/web/src/main.ts

+52-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
11
import validatePackgeName from "validate-npm-package-name";
22
import type { ResultMessage } from "../worker/worker.ts";
33
import { subscribeRenderer } from "./renderer.ts";
4-
import { updateState, type PackageInfo, type ParsedPackageSpec, getState } from "./state.ts";
4+
import { updateState, type PackageInfo, type ParsedPackageSpec, getState, subscribe, type State } from "./state.ts";
55
import { shallowEqual } from "./utils/shallowEqual.ts";
66

77
// Good grief https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
88
const semverRegex =
99
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
1010
const worker = new Worker(new URL("../worker/worker.ts", import.meta.url), { type: "module" });
11-
worker.onmessage = (event: MessageEvent<ResultMessage>) => {
11+
worker.onmessage = async (event: MessageEvent<ResultMessage>) => {
1212
updateState((state) => {
1313
state.checks = event.data.data;
1414
state.isLoading = false;
1515
state.message = undefined;
1616
});
17+
18+
const params = new URLSearchParams(location.search);
19+
const state = getState();
20+
if (state.packageInfo.parsed) {
21+
params.set(
22+
"p",
23+
`${state.packageInfo.parsed.packageName}${
24+
state.packageInfo.info?.version ? `@${state.packageInfo.info.version}` : ""
25+
}`
26+
);
27+
history.replaceState(null, "", `?${params}`);
28+
}
1729
};
1830

1931
subscribeRenderer({
20-
onPackageNameInput: debounce(onPackageNameInput, 300),
32+
onPackageNameInput,
2133
onCheck,
2234
onSelectFile: async (file) => {
2335
const arrayBuffer = await file.arrayBuffer();
@@ -26,7 +38,26 @@ subscribeRenderer({
2638
},
2739
});
2840

29-
async function onPackageNameInput(value: string) {
41+
subscribe(debounce(getPackageInfo, 300));
42+
43+
if (location.search) {
44+
const packageNameInput = document.getElementById("package-spec") as HTMLInputElement;
45+
const params = new URLSearchParams(location.search);
46+
const packageSpec = params.get("p");
47+
if (packageSpec) {
48+
packageNameInput.value = packageSpec;
49+
onPackageNameInput(packageSpec);
50+
getPackageInfo().then(() => {
51+
const info = getState().packageInfo.info;
52+
console.log(info);
53+
if (info && info.size && info.size < 1_000_000 && !navigator.connection?.saveData) {
54+
onCheck();
55+
}
56+
});
57+
}
58+
}
59+
60+
function onPackageNameInput(value: string) {
3061
value = value.trim();
3162
if (!value) {
3263
updateState((state) => {
@@ -49,15 +80,20 @@ async function onPackageNameInput(value: string) {
4980
});
5081
return;
5182
}
52-
5383
if (!shallowEqual(getState().packageInfo.parsed, parsed.data)) {
5484
updateState((state) => {
5585
state.packageInfo.parsed = parsed.data;
5686
state.checks = undefined;
5787
});
88+
}
89+
}
5890

91+
async function getPackageInfo(prevState?: State) {
92+
const state = getState();
93+
const parsed = state.packageInfo.parsed;
94+
if (parsed && (!prevState || !shallowEqual(prevState.packageInfo.parsed, parsed))) {
5995
try {
60-
const info = await getPackageInfo(parsed.data);
96+
const info = await fetchPackageInfo(parsed);
6197
updateState((state) => {
6298
state.packageInfo.info = info;
6399
state.message = {
@@ -79,10 +115,16 @@ async function onPackageNameInput(value: string) {
79115
}
80116
}
81117

82-
function onCheck() {
118+
async function onCheck() {
119+
if (!getState().packageInfo.parsed) {
120+
return;
121+
}
122+
if (!getState().packageInfo.info) {
123+
await getPackageInfo();
124+
}
83125
const { packageInfo } = getState();
84126
if (packageInfo.info && packageInfo.parsed) {
85-
updateState((state) => (state.isLoading = true));
127+
updateState((state) => void (state.isLoading = true));
86128
worker.postMessage({
87129
kind: "check-package",
88130
packageName: packageInfo.parsed.packageName,
@@ -91,7 +133,7 @@ function onCheck() {
91133
}
92134
}
93135

94-
async function getPackageInfo({ packageName, version }: ParsedPackageSpec): Promise<PackageInfo> {
136+
async function fetchPackageInfo({ packageName, version }: ParsedPackageSpec): Promise<PackageInfo> {
95137
try {
96138
const response = await fetch(`https://registry.npmjs.org/${packageName}/${version || "latest"}`);
97139
if (!response.ok) {
@@ -100,6 +142,7 @@ async function getPackageInfo({ packageName, version }: ParsedPackageSpec): Prom
100142
const data = await response.json();
101143
return {
102144
size: data.dist.unpackedSize,
145+
version: data.version,
103146
};
104147
} catch (error) {
105148
throw new Error("Failed to get package info");

packages/web/src/renderer.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { subscribe, type State } from "./state";
1+
import { subscribe, getState } from "./state";
22
import { updateView } from "./utils/updateView";
33
import { CheckButton } from "./views/CheckButton";
44
import { ChecksTable } from "./views/ChecksTable";
@@ -50,14 +50,15 @@ export function subscribeRenderer(events: Events) {
5050

5151
subscribe(render);
5252

53-
function render(state: State) {
53+
function render() {
54+
const state = getState();
5455
updateView(messageElement, Message, { isError: state.message?.isError, text: state.message?.text || "" });
5556
updateView(problemsElement, ProblemList, {
5657
problems: state.checks?.problemSummaries,
5758
containsTypes: state.checks?.analysis.containsTypes,
5859
});
5960
updateView(resolutionsElement, ChecksTable, { checks: state.checks });
60-
updateView(checkButton, CheckButton, { disabled: !state.packageInfo.info });
61+
updateView(checkButton, CheckButton, { disabled: !state.packageInfo.parsed });
6162
updateView(detailsElement, Details, { analysis: state.checks?.analysis });
6263
}
6364
});

packages/web/src/state.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Analysis, ProblemSummary, Problem } from "@arethetypeswrong/core";
2+
import { produce } from "immer";
23

34
export interface Checks {
45
analysis: Analysis;
@@ -28,9 +29,10 @@ export interface PackageInfoState {
2829

2930
export interface PackageInfo {
3031
size: number | undefined;
32+
version: string;
3133
}
3234

33-
const state: State = {
35+
let state: State = {
3436
isLoading: false,
3537
packageInfo: {},
3638
};
@@ -41,16 +43,17 @@ type DeepReadonly<T extends object> = {
4143

4244
const subscribers: Set<(state: DeepReadonly<State>) => void> = new Set();
4345

44-
export function subscribe(callback: (state: DeepReadonly<State>) => void): () => void {
46+
export function subscribe(callback: (prevState: DeepReadonly<State>) => void): () => void {
4547
subscribers.add(callback);
4648
return () => {
4749
subscribers.delete(callback);
4850
};
4951
}
5052

51-
export function updateState(updater: (state: State) => void): void {
52-
updater(state);
53-
subscribers.forEach((callback) => callback(state));
53+
export function updateState(updater: (draftState: State) => void): void {
54+
const prevState = state;
55+
state = produce(state, updater);
56+
subscribers.forEach((callback) => callback(prevState));
5457
}
5558

5659
export function getState(): DeepReadonly<State> {

packages/web/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ export {};
22

33
declare global {
44
const COMMIT: string;
5+
interface Navigator {
6+
connection?: {
7+
saveData: boolean;
8+
};
9+
}
510
}

packages/web/worker/patchGlobal.ts

-2
This file was deleted.

packages/web/worker/worker.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import "./patchGlobal";
2-
31
import {
42
checkPackage,
53
checkTgz,

0 commit comments

Comments
 (0)