Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
*.local.*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down
42 changes: 38 additions & 4 deletions docs/demos/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import "@preact/signals-debug";
import { render } from "preact";
import { LocationProvider, Router, useLocation, lazy } from "preact-iso";
import { signal, useSignal } from "@preact/signals";
import { signal, useComputed, useSignal } from "@preact/signals";
import { setFlashingEnabled, constrainFlashToChildren } from "./render-flasher";
import "@preact/signals-debug";

// disable flashing during initial render:
setFlashingEnabled(false);
setTimeout(setFlashingEnabled, 100, true);

const demos = {
Counter,
Sum,
GlobalCounter,
DuelingCounters,
Nesting: lazy(() => import("./nesting")),
Expand Down Expand Up @@ -58,7 +59,7 @@ function displayName(name: string) {
}

function Counter() {
const count = useSignal(0, "counter");
const count = useSignal(0, { name: "counter" });

return (
<div class="card">
Expand All @@ -69,7 +70,40 @@ function Counter() {
);
}

const globalCount = signal(0, "global-counter");
function Sum() {
const a = useSignal(0, { name: "a" });
const b = useSignal(0, { name: "b" });

const sum = useComputed(() => a.value + b.value, { name: "sum" });

return (
<div class="card">
<p>
<label>
A:{" "}
<input
type="number"
value={a}
onInput={e => (a.value = +e.currentTarget.value)}
/>
</label>
</p>
<p>
<label>
B:{" "}
<input
type="number"
value={b}
onInput={e => (b.value = +e.currentTarget.value)}
/>
</label>
</p>
<output>Sum: {sum}</output>
</div>
);
}

const globalCount = signal(0, { name: "global-counter" });
function GlobalCounter({ explain = true }) {
return (
<>
Expand Down
34 changes: 34 additions & 0 deletions extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Extension artifacts
*.crx
*.pem
*.zip

# Build outputs
dist/
build/

# Node modules
node_modules/

# Temporary files
.tmp/
.cache/

# OS files
.DS_Store
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo

# Web-ext artifacts
web-ext-artifacts/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
129 changes: 129 additions & 0 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Background service worker for the extension

// Maps to store connections by tab ID
const contentConnections = new Map(); // tab ID -> content script port
const devtoolsConnections = new Map(); // tab ID -> devtools port

chrome.runtime.onConnect.addListener(port => {
if (port.name === "content-to-background") {
handleContentScriptConnection(port);
} else if (port.name === "devtools-to-background") {
handleDevToolsConnection(port);
} else {
console.warn("Unknown connection type:", port.name);
port.disconnect();
}
});

function handleContentScriptConnection(port) {
const tabId = port.sender?.tab?.id;

if (!tabId) {
console.error("Content script connection missing tab ID");
port.disconnect();
return;
}

contentConnections.set(tabId, port);

port.onMessage.addListener(message => {
// Forward message to devtools if connected
const devtoolsPort = devtoolsConnections.get(tabId);
if (devtoolsPort) {
try {
devtoolsPort.postMessage(message);
} catch (error) {
console.error("Failed to forward message to devtools:", error);
devtoolsConnections.delete(tabId);
}
} else {
console.log(
`No devtools connection for tab ${tabId}, message queued:`,
message.type
);
}
});

port.onDisconnect.addListener(() => {
contentConnections.delete(tabId);

// Notify devtools if connected
const devtoolsPort = devtoolsConnections.get(tabId);
if (devtoolsPort) {
try {
devtoolsPort.postMessage({ type: "CONTENT_SCRIPT_DISCONNECTED" });
} catch (error) {
console.error(
"Failed to notify devtools of content script disconnect:",
error
);
}
}
});
}

function handleDevToolsConnection(port) {
let tabId = null;
let isInitialized = false;

// Listen for the initial tab ID message
const tabIdListener = message => {
if (message.type === "DEVTOOLS_TAB_ID" && !isInitialized) {
tabId = message.tabId;
isInitialized = true;

devtoolsConnections.set(tabId, port);

// Remove the tab ID listener
port.onMessage.removeListener(tabIdListener);

// Set up the main message listener
port.onMessage.addListener(message => {
// Forward message to content script if connected
const contentPort = contentConnections.get(tabId);
if (contentPort) {
try {
contentPort.postMessage(message);
} catch (error) {
console.error(
"Failed to forward message to content script:",
error
);
contentConnections.delete(tabId);
}
} else {
console.log(`No content script connection for tab ${tabId}`);
}
});

// Send initial status to devtools
const contentPort = contentConnections.get(tabId);
try {
port.postMessage({
type: "BACKGROUND_READY",
contentScriptConnected: !!contentPort,
});
} catch (error) {
console.error("Failed to send initial status to devtools:", error);
}
}
};

port.onMessage.addListener(tabIdListener);

port.onDisconnect.addListener(() => {
if (tabId) {
devtoolsConnections.delete(tabId);
}
});
}

chrome.action.onClicked.addListener(tab => {
chrome.tabs.sendMessage(tab.id, { type: "OPEN_DEVTOOLS_HINT" });
});

// Clean up connections when tabs are closed
chrome.tabs.onRemoved.addListener(tabId => {
contentConnections.delete(tabId);
devtoolsConnections.delete(tabId);
});
Loading