feat(chrome-extension): add JSON Crack browser extension

Injects a Raw/Graph toggle on any page served as JSON. Graph mode
mounts jsoncrack-react in a full-viewport overlay, with click-to-inspect
node details, system-theme detection, a "Powered by JSON Crack"
attribution link, and a ToDiagram upgrade overlay when the node limit
is exceeded.

Built with Vite as an MV3 content script bundle.
This commit is contained in:
AykutSarac
2026-04-10 18:01:10 +03:00
parent 957c07b5fc
commit da0fac1998
16 changed files with 1464 additions and 0 deletions
+6
View File
@@ -58,6 +58,7 @@ JSON Crack is a tool for visualizing JSON data in a structured, interactive grap
## Integrations
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=AykutSarac.jsoncrack-vscode)
- [Chrome Extension](./apps/chrome-extension/README.md)
- [npm Package (`jsoncrack-react`)](https://www.npmjs.com/package/jsoncrack-react)
## Contributing
@@ -134,6 +135,11 @@ pnpm build:vscode
pnpm lint:vscode
pnpm lint:fix:vscode
# Chrome extension
pnpm dev:chrome
pnpm build:chrome
pnpm lint:chrome
# All workspaces
pnpm dev
pnpm build
+1
View File
@@ -0,0 +1 @@
dist
+38
View File
@@ -0,0 +1,38 @@
# JSON Crack Chrome Extension
Chrome Extension (Manifest v3) that injects a segmented control on JSON-only pages.
Parsed mode renders with local `jsoncrack-react` (no iframe).
- `Raw`: browser's default JSON response view
- `Parsed`: JSON Crack graph view
## Development
From repository root:
```sh
pnpm --filter chrome-extension dev
```
This runs `vite build --watch` and updates `apps/chrome-extension/dist`.
## Build
From repository root:
```sh
pnpm --filter chrome-extension build
```
Then load the extension in Chrome:
1. Open `chrome://extensions`
2. Enable **Developer mode**
3. Click **Load unpacked**
4. Select `apps/chrome-extension/dist`
## Usage
1. Open a URL that responds with raw JSON.
2. Use the floating `Raw | Parsed` segmented control.
3. Press `Esc` to quickly return to `Raw`.
+25
View File
@@ -0,0 +1,25 @@
{
"name": "chrome-extension",
"private": true,
"version": "0.1.0",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"dev": "vite build --watch",
"build": "vite build",
"lint": "tsc --project tsconfig.json --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"jsoncrack-react": "workspace:*",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"typescript": "5.9.3",
"vite": "^8.0.0"
},
"packageManager": "pnpm@10.20.0"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,31 @@
{
"manifest_version": 3,
"name": "JSON Crack",
"short_name": "JSON Crack",
"description": "Visualize JSON pages as an interactive graph. Toggle between raw text and an auto-generated diagram.",
"version": "0.1.0",
"author": "Aykut Saraç",
"homepage_url": "https://jsoncrack.com",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"action": {
"default_title": "JSON Crack",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"],
"run_at": "document_idle"
}
]
}
@@ -0,0 +1,307 @@
#jsoncrack-toggle,
#jsoncrack-graph-overlay,
#jsoncrack-node-modal-backdrop {
--jsoncrack-font:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--jsoncrack-font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
}
#jsoncrack-toggle {
position: fixed;
top: 12px;
right: 12px;
z-index: 2147483647;
font-family: var(--jsoncrack-font);
}
#jsoncrack-toggle .jsoncrack-segmented {
display: inline-flex;
border: 1px solid #a8a8a8;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
#jsoncrack-toggle .jsoncrack-toggle-btn {
border: 0;
border-right: 1px solid #b4b4b4;
background: linear-gradient(180deg, #f2f2f2 0%, #dadada 100%);
color: #383838;
height: 30px;
padding: 0 12px;
font-size: 13px;
line-height: 1;
font-weight: 600;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
}
#jsoncrack-toggle .jsoncrack-toggle-btn:last-child {
border-right: 0;
}
#jsoncrack-toggle .jsoncrack-toggle-btn.is-active {
background: linear-gradient(180deg, #dcdcdc 0%, #c6c6c6 100%);
color: #232323;
}
#jsoncrack-graph-overlay {
position: fixed;
inset: 0;
z-index: 2147483646;
background: #ffffff;
font-family: var(--jsoncrack-font);
}
@media (prefers-color-scheme: dark) {
#jsoncrack-graph-overlay {
background: #0b0f17;
}
}
#jsoncrack-graph-overlay[hidden] {
display: none !important;
}
#jsoncrack-attribution {
position: fixed;
right: 0;
bottom: 0;
z-index: 2147483647;
padding: 8px 16px;
background: #0b0f17;
color: #ffffff;
font-size: 14px;
font-weight: 600;
text-decoration: none;
font-family: var(--jsoncrack-font);
}
#jsoncrack-attribution:hover {
text-decoration: underline;
}
#jsoncrack-upgrade-overlay {
position: absolute;
inset: 0;
z-index: 40;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
background: rgba(17, 17, 17, 0.6);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
font-family: var(--jsoncrack-font);
}
#jsoncrack-upgrade-card {
position: relative;
width: 90%;
max-width: 460px;
padding: 40px 48px;
border-radius: 16px;
background: rgba(37, 38, 43, 0.97);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.4),
0 1px 3px rgba(0, 0, 0, 0.2);
color: #f1f3f5;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
animation: jsoncrack-upgrade-fade-in 0.4s ease-out;
}
@keyframes jsoncrack-upgrade-fade-in {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.jsoncrack-upgrade-logo {
width: 48px;
height: 48px;
}
.jsoncrack-upgrade-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 4px;
background: rgba(18, 184, 134, 0.15);
color: #0ca678;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.jsoncrack-upgrade-title {
margin: 0;
color: #ffffff;
font-size: 24px;
font-weight: 700;
line-height: 1.2;
text-align: center;
}
.jsoncrack-upgrade-desc {
margin: 0;
max-width: 360px;
color: #adb5bd;
font-size: 14px;
line-height: 1.5;
text-align: center;
}
.jsoncrack-upgrade-link {
color: #20c997;
font-weight: 500;
text-decoration: none;
}
.jsoncrack-upgrade-link:hover {
text-decoration: underline;
}
.jsoncrack-upgrade-cta {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 12px 16px;
border-radius: 8px;
background: #0ca678;
color: #ffffff;
font-size: 15px;
font-weight: 600;
text-decoration: none;
transition: background 120ms ease;
}
.jsoncrack-upgrade-cta:hover {
background: #099268;
}
#jsoncrack-react-root {
width: 100%;
height: 100%;
}
#jsoncrack-graph-error {
margin: 20px;
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #7f1d1d;
background: rgba(220, 38, 38, 0.15);
color: #fee2e2;
font: 500 14px/1.4 var(--jsoncrack-font);
}
#jsoncrack-graph-status {
margin: 20px;
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #1e3a8a;
background: rgba(59, 130, 246, 0.16);
color: #dbeafe;
font: 500 14px/1.4 var(--jsoncrack-font);
}
#jsoncrack-node-modal-backdrop {
position: fixed;
inset: 0;
z-index: 2147483646;
background: rgba(1, 6, 15, 0.66);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
#jsoncrack-node-modal {
width: min(680px, calc(100vw - 32px));
max-height: min(80vh, 760px);
overflow: auto;
border-radius: 12px;
border: 1px solid #334155;
background: #0f172a;
color: #e2e8f0;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
font-family: var(--jsoncrack-font);
}
#jsoncrack-node-modal .jsoncrack-node-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid #1f2937;
font-size: 14px;
}
#jsoncrack-node-modal .jsoncrack-node-modal-close {
border: 1px solid #334155;
border-radius: 6px;
background: #111827;
color: #cbd5e1;
width: 30px;
height: 30px;
font-size: 20px;
line-height: 1;
cursor: pointer;
}
#jsoncrack-node-modal .jsoncrack-node-modal-close:hover {
background: #1f2937;
}
#jsoncrack-node-modal .jsoncrack-node-modal-section {
padding: 12px 14px 14px;
}
#jsoncrack-node-modal .jsoncrack-node-modal-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
color: #bfdbfe;
font-size: 12px;
font-weight: 600;
}
#jsoncrack-node-modal .jsoncrack-node-modal-copy {
border: 1px solid #334155;
border-radius: 6px;
background: #111827;
color: #dbeafe;
height: 26px;
padding: 0 10px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
}
#jsoncrack-node-modal .jsoncrack-node-modal-copy:hover {
background: #1f2937;
}
#jsoncrack-node-modal pre {
margin: 0;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #334155;
background: #020617;
color: #dbeafe;
white-space: pre-wrap;
overflow-wrap: anywhere;
font: 500 12px/1.45 var(--jsoncrack-font-mono);
}
@@ -0,0 +1,408 @@
import type { JSONCrackProps, NodeData } from "jsoncrack-react";
import type { ReactNode } from "react";
import { useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";
import jsoncrackStyles from "jsoncrack-react/style.css?inline";
import localStyles from "./content-script.css?inline";
type ThemeMode = "light" | "dark";
type JSONCrackComponentType = (props: JSONCrackProps) => ReactNode;
const NODE_MODAL_BACKDROP_ID = "jsoncrack-node-modal-backdrop";
const GRAPH_OVERLAY_ID = "jsoncrack-graph-overlay";
const TOGGLE_ID = "jsoncrack-toggle";
let jsonCrackComponentPromise: Promise<JSONCrackComponentType> | null = null;
const loadJsonCrackComponent = async (): Promise<JSONCrackComponentType> => {
if (jsonCrackComponentPromise) {
return jsonCrackComponentPromise;
}
jsonCrackComponentPromise = (async () => {
// ELK (the layout engine used by reaflow) tries to spawn a Web Worker
// on first use. On JSON pages with a strict `default-src 'none'` CSP,
// worker creation throws. Temporarily shadow `Worker` for the duration
// of the import + initial layout so ELK falls back to its sync path,
// then restore it so the host page's own workers keep working.
const originalWorker = (globalThis as { Worker?: typeof Worker }).Worker;
try {
(globalThis as { Worker?: typeof Worker }).Worker = undefined;
const mod = await import("jsoncrack-react");
return mod.JSONCrack as JSONCrackComponentType;
} finally {
(globalThis as { Worker?: typeof Worker }).Worker = originalWorker;
}
})();
return jsonCrackComponentPromise;
};
(() => {
if (window.top !== window) return;
if (!document.body) return;
if (document.getElementById(TOGGLE_ID)) return;
const source = getJsonSource();
if (!source) return;
injectStyles();
injectToggle(source);
})();
function injectStyles() {
const styleTag = document.createElement("style");
styleTag.id = "jsoncrack-inline-style";
styleTag.textContent = `${jsoncrackStyles}\n${localStyles}`;
document.documentElement.appendChild(styleTag);
}
function getJsonSource() {
const body = document.body;
const contentType = (document.contentType || "").toLowerCase();
const pickText = (): string => {
if (contentType.includes("json")) {
return body.innerText || body.textContent || "";
}
// Browsers render standalone JSON responses as a single <pre> inside
// <body>. Some built-in viewers wrap that <pre> in extra containers,
// so look for exactly one <pre> anywhere in the body.
const pres = body.querySelectorAll("pre");
if (pres.length === 1) {
return pres[0].textContent || "";
}
return "";
};
const raw = pickText().trim();
if (!raw) return null;
if (!startsLikeJson(raw)) return null;
try {
JSON.parse(raw);
return raw;
} catch {
return null;
}
}
function startsLikeJson(value: string) {
return value.startsWith("{") || value.startsWith("[");
}
function detectTheme(): ThemeMode {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function useSystemTheme(): ThemeMode {
const [theme, setTheme] = useState<ThemeMode>(detectTheme);
useEffect(() => {
const media = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (event: MediaQueryListEvent) => {
setTheme(event.matches ? "dark" : "light");
};
media.addEventListener("change", handler);
return () => media.removeEventListener("change", handler);
}, []);
return theme;
}
function injectToggle(rawJson: string) {
const toggleRoot = document.createElement("div");
toggleRoot.id = TOGGLE_ID;
toggleRoot.innerHTML = [
'<div class="jsoncrack-segmented" role="group" aria-label="JSON view mode">',
'<button class="jsoncrack-toggle-btn is-active" type="button" data-mode="raw" aria-pressed="true">Raw</button>',
'<button class="jsoncrack-toggle-btn" type="button" data-mode="graph" aria-pressed="false">Graph</button>',
"</div>",
].join("");
const graphOverlay = document.createElement("div");
graphOverlay.id = GRAPH_OVERLAY_ID;
graphOverlay.hidden = true;
const reactRootContainer = document.createElement("div");
reactRootContainer.id = "jsoncrack-react-root";
graphOverlay.appendChild(reactRootContainer);
const attribution = document.createElement("a");
attribution.id = "jsoncrack-attribution";
attribution.href =
"https://jsoncrack.com/editor?utm_source=jsoncrack&utm_medium=chrome_extension&utm_campaign=attribution";
attribution.target = "_blank";
attribution.rel = "noopener noreferrer";
attribution.textContent = "Powered by JSON Crack";
graphOverlay.appendChild(attribution);
document.body.appendChild(graphOverlay);
document.body.appendChild(toggleRoot);
const rawButton = toggleRoot.querySelector<HTMLButtonElement>('[data-mode="raw"]');
const graphButton = toggleRoot.querySelector<HTMLButtonElement>('[data-mode="graph"]');
if (!rawButton || !graphButton) return;
let graphRoot: ReturnType<typeof createRoot> | null = null;
const mountGraphView = () => {
if (graphRoot) return;
graphRoot = createRoot(reactRootContainer);
graphRoot.render(<GraphView rawJson={rawJson} />);
};
const unmountGraphView = () => {
if (!graphRoot) return;
graphRoot.unmount();
graphRoot = null;
reactRootContainer.textContent = "";
};
const setMode = (mode: "raw" | "graph") => {
const isGraph = mode === "graph";
graphOverlay.hidden = !isGraph;
rawButton.classList.toggle("is-active", !isGraph);
graphButton.classList.toggle("is-active", isGraph);
rawButton.setAttribute("aria-pressed", String(!isGraph));
graphButton.setAttribute("aria-pressed", String(isGraph));
if (isGraph) {
// Wait for the overlay to be laid out before mounting so the graph
// viewport initializes with the correct container dimensions and
// centerOnLayout fits the graph correctly.
window.requestAnimationFrame(() => {
if (graphOverlay.hidden) return;
mountGraphView();
});
} else {
unmountGraphView();
}
};
rawButton.addEventListener("click", () => setMode("raw"));
graphButton.addEventListener("click", () => setMode("graph"));
window.addEventListener(
"keydown",
event => {
if (event.key !== "Escape") return;
if (graphOverlay.hidden) return;
if (document.getElementById(NODE_MODAL_BACKDROP_ID)) return;
setMode("raw");
},
// Use capture so we beat host-page handlers that might stopPropagation.
true
);
}
const normalizeNodeData = (nodeRows: NodeData["text"]) => {
if (!nodeRows || nodeRows.length === 0) return "{}";
if (nodeRows.length === 1 && !nodeRows[0].key) return `${nodeRows[0].value}`;
const obj: Record<string, unknown> = {};
nodeRows.forEach(row => {
if (row.type !== "array" && row.type !== "object" && row.key) {
obj[row.key] = row.value;
}
});
return JSON.stringify(obj, null, 2);
};
const jsonPathToString = (path?: NodeData["path"]) => {
if (!path || path.length === 0) return "$";
const segments = path.map(seg => (typeof seg === "number" ? seg : `"${seg}"`));
return `$[${segments.join("][")}]`;
};
function NodeModal({ nodeData, onClose }: { nodeData: NodeData | null; onClose: () => void }) {
const [copiedField, setCopiedField] = useState<"content" | "path" | null>(null);
const nodeContent = useMemo(() => normalizeNodeData(nodeData?.text ?? []), [nodeData]);
const jsonPath = useMemo(() => jsonPathToString(nodeData?.path), [nodeData]);
useEffect(() => {
if (!nodeData) return;
const onEscape = (event: KeyboardEvent) => {
if (event.key !== "Escape") return;
event.preventDefault();
event.stopPropagation();
onClose();
};
window.addEventListener("keydown", onEscape, true);
return () => {
window.removeEventListener("keydown", onEscape, true);
};
}, [nodeData, onClose]);
if (!nodeData) return null;
const handleCopy = async (field: "content" | "path", text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedField(field);
window.setTimeout(() => {
setCopiedField(current => (current === field ? null : current));
}, 1200);
} catch {
setCopiedField(null);
}
};
return (
<div id={NODE_MODAL_BACKDROP_ID} onClick={onClose}>
<div
id="jsoncrack-node-modal"
role="dialog"
aria-modal="true"
onClick={event => event.stopPropagation()}
>
<div className="jsoncrack-node-modal-header">
<strong>Node Content</strong>
<button
type="button"
className="jsoncrack-node-modal-close"
onClick={onClose}
aria-label="Close modal"
>
×
</button>
</div>
<div className="jsoncrack-node-modal-section">
<div className="jsoncrack-node-modal-row">
<span>Content</span>
<button
type="button"
className="jsoncrack-node-modal-copy"
onClick={() => handleCopy("content", nodeContent)}
>
{copiedField === "content" ? "Copied" : "Copy"}
</button>
</div>
<pre>{nodeContent}</pre>
</div>
<div className="jsoncrack-node-modal-section">
<div className="jsoncrack-node-modal-row">
<span>JSON Path</span>
<button
type="button"
className="jsoncrack-node-modal-copy"
onClick={() => handleCopy("path", jsonPath)}
>
{copiedField === "path" ? "Copied" : "Copy"}
</button>
</div>
<pre>{jsonPath}</pre>
</div>
</div>
</div>
);
}
// NOTE: `rawJson` is captured once at injection time. Single-Page-App JSON
// viewers that swap the payload in place without a full reload won't update
// the graph. That's acceptable for the current target (browser JSON viewers
// on static responses) — revisit if we add SPA support.
function GraphView({ rawJson }: { rawJson: string }) {
const theme = useSystemTheme();
const [JSONCrackComponent, setJSONCrackComponent] = useState<JSONCrackComponentType | null>(null);
const [componentError, setComponentError] = useState<string | null>(null);
const [selectedNode, setSelectedNode] = useState<NodeData | null>(null);
useEffect(() => {
let active = true;
loadJsonCrackComponent()
.then(component => {
if (!active) return;
setJSONCrackComponent(() => component);
})
.catch((error: unknown) => {
if (!active) return;
const message = error instanceof Error ? error.message : "Unable to load graph renderer.";
setComponentError(message);
});
return () => {
active = false;
};
}, []);
const parsedJson = useMemo(() => {
try {
return JSON.parse(rawJson);
} catch {
return null;
}
}, [rawJson]);
if (parsedJson === null) {
return <div id="jsoncrack-graph-error">JSON parsing failed for graph mode.</div>;
}
if (componentError) {
return <div id="jsoncrack-graph-error">Graph renderer error: {componentError}</div>;
}
if (!JSONCrackComponent) {
return <div id="jsoncrack-graph-status">Loading graph renderer...</div>;
}
return (
<div style={{ width: "100%", height: "100%" }}>
<JSONCrackComponent
json={parsedJson}
theme={theme}
showControls
showGrid
centerOnLayout
onNodeClick={node => setSelectedNode(node)}
renderNodeLimitExceeded={() => <NodeLimitUpgrade />}
/>
<NodeModal nodeData={selectedNode} onClose={() => setSelectedNode(null)} />
</div>
);
}
function NodeLimitUpgrade() {
return (
<div id="jsoncrack-upgrade-overlay">
<div id="jsoncrack-upgrade-card">
<img
src="https://todiagram.com/logo.svg"
alt="ToDiagram"
width={48}
height={48}
className="jsoncrack-upgrade-logo"
/>
<span className="jsoncrack-upgrade-badge">Upgrade Required</span>
<h2 className="jsoncrack-upgrade-title">Your diagram is too large</h2>
<p className="jsoncrack-upgrade-desc">
JSON Crack can&apos;t render this file.{" "}
<a
href="https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=chrome_extension&utm_campaign=data_limit"
target="_blank"
rel="noopener noreferrer"
className="jsoncrack-upgrade-link"
>
ToDiagram
</a>{" "}
handles large datasets with ease.
</p>
<a
href="https://todiagram.com/editor?utm_source=jsoncrack&utm_medium=chrome_extension&utm_campaign=data_limit&modal=upgrade&format=json&example=true"
target="_blank"
rel="noopener noreferrer"
className="jsoncrack-upgrade-cta"
>
Continue with ToDiagram
</a>
</div>
</div>
);
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module "*.css?inline" {
const content: string;
export default content;
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"noImplicitAny": false
},
"include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"],
"exclude": ["dist", "node_modules"]
}
+30
View File
@@ -0,0 +1,30 @@
import { defineConfig } from "vite";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
build: {
lib: {
entry: path.resolve(__dirname, "src/content-script.tsx"),
formats: ["iife"],
name: "JSONCrackContentScript",
fileName: () => "content-script.js",
},
cssCodeSplit: false,
outDir: "dist",
emptyOutDir: true,
target: "es2022",
sourcemap: false,
rollupOptions: {
output: {
banner:
"var Worker = undefined; var process = globalThis.process || (globalThis.process = { env: { NODE_ENV: 'production' } });",
},
},
},
});
+4
View File
@@ -14,14 +14,18 @@
"dev": "turbo run dev",
"dev:www": "turbo run dev --filter=www",
"dev:vscode": "turbo run watch --filter=vscode",
"dev:chrome": "turbo run dev --filter=chrome-extension",
"build": "turbo run build",
"build:www": "turbo run build --filter=www",
"build:vscode": "turbo run build --filter=vscode",
"build:chrome": "turbo run build --filter=chrome-extension",
"start": "turbo run start",
"lint": "turbo run lint",
"lint:vscode": "turbo run lint --filter=vscode",
"lint:chrome": "turbo run lint --filter=chrome-extension",
"lint:fix": "turbo run lint:fix",
"lint:fix:vscode": "turbo run lint:fix --filter=vscode",
"test": "turbo run test",
"analyze": "turbo run analyze",
"clean": "turbo run clean"
},
+590
View File
File diff suppressed because it is too large Load Diff