diff --git a/build.ts b/build.ts index 0cfdae22..612b7e19 100644 --- a/build.ts +++ b/build.ts @@ -1,18 +1,34 @@ -import * as path from 'node:path' -import * as url from 'node:url' +// import * as url from 'node:url' import * as fs from 'node:fs' -import * as assert from 'node:assert/strict' -import * as threads from 'node:worker_threads' +import * as cp from 'node:child_process' +// import * as path from 'node:path' +// import * as assert from 'node:assert/strict' +// import * as threads from 'node:worker_threads' import * as esb from 'esbuild' -import ts from 'typescript' - -type Worker_Data = { - is_dev: boolean, - dist_dirname: string, - ts_entries: string[], +// import ts from 'typescript' + +function is_env_truthy(value: string | undefined): boolean { + if (!value) return false + value = value.toLowerCase().trim() + return value === 'true' + || value === '"1"' + || value === '1' + || value === 'yes' + || value === 'y' } -const filename = url.fileURLToPath(import.meta.url) +export const CI = is_env_truthy(process.env['CI']) + || is_env_truthy(process.env['GITHUB_ACTIONS']) + || !!process.env['TURBO_HASH'] + +// type Worker_Data = { +// is_dev: boolean, +// base_path: string, +// dist_path: string, +// ts_entries: string[], +// } + +// const filename = url.fileURLToPath(import.meta.url) // const dirname = path.dirname(filename) export const DEFAULT_EXTERNAL_DEPS: string[] = [ @@ -22,10 +38,10 @@ export const DEFAULT_EXTERNAL_DEPS: string[] = [ ] export function get_external_deps_from_pkg(pkg_filename: string): string[] { - let pkg = JSON.parse(fs.readFileSync(pkg_filename) as any) as any + let pkg = JSON.parse(fs.readFileSync(pkg_filename) as any) as any let deps = Object.keys({...pkg?.peerDependencies, ...pkg?.dependencies}) deps.push(...DEFAULT_EXTERNAL_DEPS) - return deps + return deps } export function get_is_dev_from_args(): boolean { @@ -47,123 +63,144 @@ export function get_common_esbuild_options(is_dev: boolean, dist_dirname: string } } -function main() { - - if (threads.isMainThread) - return - - /* Worker - runs the ts program */ - - const data = threads.workerData as Worker_Data - - const port = threads.parentPort - assert.ok(port != null) - - const options = get_tsc_options(data.dist_dirname) - - /* Watch - never terminates */ - if (data.is_dev) { - const host = ts.createWatchCompilerHost( - data.ts_entries, - options, - ts.sys, - undefined, - report_diagnostic, - report_watch_status_changed, - ) - ts.createWatchProgram(host) - } - /* Emit once and exit */ - else { - let begin = performance.now() - ts.createProgram(data.ts_entries, options).emit() - // eslint-disable-next-line no-console - console.log(`DTS complete in ${(performance.now()-begin).toFixed(2)}ms`) - process.exit(0) - } -} - export async function build( - options: esb.BuildOptions[], - ts_entries: string[], - is_dev: boolean, - dist_dirname: string = path.join(process.cwd(), `dist`), + options: esb.BuildOptions[], + is_dev: boolean, + base_path: string, + dist_path: string, ): Promise { - /* Clear dist when building to prod */ - if (!is_dev) { - fs.rmSync(dist_dirname, {recursive: true, force: true}) - } + /* Clear dist when building to prod */ + if (!is_dev) { + fs.rmSync(dist_path, {recursive: true, force: true}) + } - const worker = new threads.Worker(filename, { - workerData: {is_dev, dist_dirname, ts_entries} satisfies Worker_Data - }) + let tsc_args = [] - worker.on('error', (error) => { - // eslint-disable-next-line no-console - console.error(`Worker error:`, error) - }) - - /* Watch - never terminates */ - if (is_dev) { - for (const option of options) { - esb.context(option) - .then(ctx => ctx.watch()) - } - } - /* Build once - wait for all to finish */ - else { - let begin = performance.now() - await Promise.all(options.map(option => esb.build(option))) - // eslint-disable-next-line no-console - console.log(`JS built in ${(performance.now()-begin).toFixed(2)}ms`) - } -} + if (CI) { + tsc_args.push('--noEmitOnError') + } + if (is_dev) { + tsc_args.push('--watch', '--preserveWatchOutput') + } -const format_host: ts.FormatDiagnosticsHost = { - getCurrentDirectory: () => process.cwd(), - getCanonicalFileName: (fileName) => fileName, - getNewLine: () => ts.sys.newLine -} + let tsc_process = cp.spawn('tsc', tsc_args, { + cwd: base_path, + stdio: 'inherit', + }) -function report_diagnostic(diagnostic: ts.Diagnostic) { - // eslint-disable-next-line no-console - console.error(ts.formatDiagnosticsWithColorAndContext([diagnostic], format_host)) -} -function report_diagnostics(diagnostics: ts.Diagnostic[]) { - // eslint-disable-next-line no-console - console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, format_host)) -} -function report_watch_status_changed(diagnostic: ts.Diagnostic) { - // eslint-disable-next-line no-console - console.info(ts.formatDiagnosticsWithColorAndContext([diagnostic], format_host)) -} + tsc_process.on('error', error => { + // eslint-disable-next-line no-console + console.error('TSC process error:', error) + if (!is_dev) process.exit(1) + }) -export function get_tsc_options(dist_dirname: string): ts.CompilerOptions { - - let ts_config_file = ts.findConfigFile(process.cwd(), ts.sys.fileExists, 'tsconfig.json') - if (!ts_config_file) throw Error('tsconfig.json not found') - - let {config, error} = ts.readConfigFile(ts_config_file, ts.sys.readFile) - if (error) { - report_diagnostic(error) - } - - let {options, errors} = ts.parseJsonConfigFileContent(config, ts.sys, process.cwd()) - if (errors.length > 0) { - report_diagnostics(errors) - } - - return { - ...options, - outDir: dist_dirname, - emitDeclarationOnly: true, - noEmit: false, - noEmitOnError: false, - declaration: true, - sourceMap: true, - } + // const worker = new threads.Worker(filename, { + // workerData: {is_dev, dist_path, base_path, ts_entries} satisfies Worker_Data, + // argv: process.argv, + // env: process.env, + // }) + + // worker.on('error', (error) => { + // // eslint-disable-next-line no-console + // console.error(`Worker error:`, error) + // }) + + /* Watch - never terminates */ + if (is_dev) { + for (const option of options) { + esb.context(option) + .then(ctx => ctx.watch()) + } + } + /* Build once - wait for all to finish */ + else { + let begin = performance.now() + await Promise.all(options.map(option => esb.build(option))) + // eslint-disable-next-line no-console + console.log(`JS built in ${(performance.now()-begin).toFixed(2)}ms`) + } } - -main() \ No newline at end of file +// function main() { + +// if (threads.isMainThread) +// return + +// /* Worker - runs the ts program */ + +// const data = threads.workerData as Worker_Data + +// const port = threads.parentPort +// assert.ok(port != null) + +// const options = get_tsc_options(data.base_path) + +// /* Watch - never terminates */ +// if (data.is_dev) { +// const host = ts.createWatchCompilerHost( +// data.ts_entries, +// options, +// ts.sys, +// undefined, +// report_diagnostic, +// report_watch_status_changed, +// ) +// ts.createWatchProgram(host) +// } +// /* Emit once and exit */ +// else { +// let begin = performance.now() +// ts.createProgram(data.ts_entries, options).emit() +// // eslint-disable-next-line no-console +// console.log(`DTS complete in ${(performance.now()-begin).toFixed(2)}ms`) +// process.exit(0) +// } +// } + +// const format_host: ts.FormatDiagnosticsHost = { +// getCurrentDirectory: () => process.cwd(), +// getCanonicalFileName: filename => filename, +// getNewLine: () => ts.sys.newLine +// } + +// function report_diagnostic(diagnostic: ts.Diagnostic) { +// // eslint-disable-next-line no-console +// console.error(ts.formatDiagnosticsWithColorAndContext([diagnostic], format_host)) +// } +// function report_diagnostics(diagnostics: ts.Diagnostic[]) { +// // eslint-disable-next-line no-console +// console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, format_host)) +// } +// function report_watch_status_changed(diagnostic: ts.Diagnostic) { +// // eslint-disable-next-line no-console +// console.info(ts.formatDiagnosticsWithColorAndContext([diagnostic], format_host)) +// } + +// export function get_tsc_options(base_path: string): ts.CompilerOptions { + +// let ts_config_file = ts.findConfigFile(base_path, ts.sys.fileExists) +// if (!ts_config_file) throw Error('tsconfig.json not found') + +// let {config, error} = ts.readConfigFile(ts_config_file, ts.sys.readFile) +// if (error) { +// report_diagnostic(error) +// } + +// let {options, errors} = ts.parseJsonConfigFileContent(config, ts.sys, base_path) +// if (errors.length > 0) { +// report_diagnostics(errors) +// } + +// return { +// ...options, +// emitDeclarationOnly: true, +// noEmit: false, +// noEmitOnError: CI, +// declaration: true, +// declarationMap: true, +// } +// } + + +// main() \ No newline at end of file diff --git a/configs/tsconfig.base.json b/configs/tsconfig.base.json index 4f49f178..a3adbb60 100644 --- a/configs/tsconfig.base.json +++ b/configs/tsconfig.base.json @@ -13,8 +13,6 @@ "forceConsistentCasingInFileNames": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, - "allowJs": true, - "checkJs": true, "emitDeclarationOnly": true, "outDir": "types", "types": ["@total-typescript/ts-reset"] diff --git a/package.json b/package.json index 4e9a6794..ce5b3cf8 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@changesets/cli": "^2.27.10", + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", "@nothing-but/eslint-plugin": "^0.2.3", "@playwright/test": "^1.49.0", "@solid-devtools/shared": "file:packages/shared", @@ -35,18 +36,17 @@ "@types/node": "^22.10.1", "@typescript-eslint/eslint-plugin": "^8.17.0", "@typescript-eslint/parser": "^8.17.0", + "@unocss/core": "0.65.1", + "@unocss/preset-uno": "0.65.1", "cross-env": "^7.0.3", "esbuild": "^0.24.0", "esbuild-plugin-solid": "^0.6.0", "eslint": "^9.16.0", - "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", "eslint-plugin-no-only-tests": "^3.3.0", "jsdom": "^25.0.1", "solid-js": "^1.9.3", "turbo": "^1.10.16", "typescript": "^5.7.2", - "@unocss/core": "0.65.1", - "@unocss/preset-uno": "0.65.1", "vite": "6.0.3", "vite-plugin-solid": "^2.11.0", "vitest": "^2.1.8" @@ -55,5 +55,10 @@ "engines": { "node": ">=22", "pnpm": ">=9.14.0" + }, + "pnpm": { + "patchedDependencies": { + "solid-js": "patches/solid-js.patch" + } } } diff --git a/packages/debugger/build.ts b/packages/debugger/build.ts index 439e324c..cc6969a1 100644 --- a/packages/debugger/build.ts +++ b/packages/debugger/build.ts @@ -50,13 +50,10 @@ async function main() { external: external, }] - const ts_entries = [ - entry_index_filename, - entry_setup_filename, - entry_types_filename, - ] - - await build.build(esb_options, ts_entries, is_dev) + await build.build(esb_options, + is_dev, + dirname, + dist_dirname) } diff --git a/packages/debugger/package.json b/packages/debugger/package.json index 0fbcacf3..a80957fc 100644 --- a/packages/debugger/package.json +++ b/packages/debugger/package.json @@ -14,7 +14,7 @@ "url": "https://github.com/thetarnav/solid-devtools/issues" }, "private": false, - "sideEffects": false, + "sideEffects": true, "publishConfig": { "access": "public" }, @@ -74,9 +74,6 @@ "peerDependencies": { "solid-js": "^1.9.0" }, - "devDependencies": { - "solid-js": "^1.9.3" - }, "keywords": [ "solid", "devtools", diff --git a/packages/debugger/src/dependency/test/collect.test.ts b/packages/debugger/src/dependency/test/collect.test.ts index f925ca79..db0357fd 100644 --- a/packages/debugger/src/dependency/test/collect.test.ts +++ b/packages/debugger/src/dependency/test/collect.test.ts @@ -1,42 +1,39 @@ -import {batch, createComputed, createMemo, createRoot, createSignal} from 'solid-js' -import {beforeEach, describe, expect, it, vi} from 'vitest' +import * as s from 'solid-js' +import * as test from 'vitest' import {NodeType} from '../../main/constants.ts' import {ObjectType, getSdtId} from '../../main/id.ts' -import SolidApi from '../../main/solid-api.ts' import type {NodeID, Solid} from '../../main/types.ts' import {type SerializedDGraph, collectDependencyGraph} from '../collect.ts' -const {getOwner} = SolidApi - let mockLAST_ID = 0 -beforeEach(() => { +test.beforeEach(() => { mockLAST_ID = 0 }) -vi.mock('../../main/get-id', () => ({getNewSdtId: () => '#' + mockLAST_ID++})) +test.vi.mock('../../main/get-id', () => ({getNewSdtId: () => '#' + mockLAST_ID++})) -describe('collectDependencyGraph', () => { - it('should collect dependency graph', () => { +test.describe('collectDependencyGraph', () => { + test.it('should collect dependency graph', () => { let rootOwner!: Solid.Root let subRootOwner!: Solid.Root - const [e] = createSignal(0, {name: 's-e'}) + const [e] = s.createSignal(0, {name: 's-e'}) - createRoot(() => { - const [a] = createSignal(0, {name: 's-a'}) - const [b] = createSignal(0, {name: 's-b'}) - const [c] = createSignal(0, {name: 's-c'}) + s.createRoot(() => { + const [a] = s.createSignal(0, {name: 's-a'}) + const [b] = s.createSignal(0, {name: 's-b'}) + const [c] = s.createSignal(0, {name: 's-c'}) - const memoA = createMemo(() => a() + e(), null, {name: 'm-a'}) + const memoA = s.createMemo(() => a() + e(), null, {name: 'm-a'}) - rootOwner = getOwner()! as Solid.Root + rootOwner = Solid$$!.getOwner()! as Solid.Root - createRoot(_ => { - const [d] = createSignal(0, {name: 's-d'}) - const memoB = createMemo(() => b() + c(), null, {name: 'm-b'}) + s.createRoot(_ => { + const [d] = s.createSignal(0, {name: 's-d'}) + const memoB = s.createMemo(() => b() + c(), null, {name: 'm-b'}) - subRootOwner = getOwner()! as Solid.Root + subRootOwner = Solid$$!.getOwner()! as Solid.Root - createComputed( + s.createComputed( () => { memoA() memoB() @@ -81,7 +78,7 @@ describe('collectDependencyGraph', () => { }, }) - expect(result.graph, 'graph of computedOwner').toEqual({ + test.expect(result.graph, 'graph of computedOwner').toEqual({ [nodes.computedId]: { name: 'c', type: NodeType.Computation, @@ -148,7 +145,7 @@ describe('collectDependencyGraph', () => { }, } satisfies SerializedDGraph.Graph) - expect(result.graph, 'is JSON-serializable').toMatchObject( + test.expect(result.graph, 'is JSON-serializable').toMatchObject( JSON.parse(JSON.stringify(result.graph)) as any, ) @@ -158,7 +155,7 @@ describe('collectDependencyGraph', () => { }, }) - expect(result.graph).toEqual({ + test.expect(result.graph).toEqual({ [nodes.computedId]: { name: 'c', type: NodeType.Computation, @@ -168,7 +165,7 @@ describe('collectDependencyGraph', () => { graph: undefined, }, [nodes.memoAId]: { - name: expect.any(String), + name: test.expect.any(String), type: NodeType.Memo, depth: 1, sources: [nodes.signalAId, nodes.signalEId], @@ -176,7 +173,7 @@ describe('collectDependencyGraph', () => { graph: undefined, }, [nodes.signalAId]: { - name: expect.any(String), + name: test.expect.any(String), type: NodeType.Signal, depth: 1, observers: [nodes.memoAId], @@ -184,7 +181,7 @@ describe('collectDependencyGraph', () => { sources: undefined, }, [nodes.signalEId]: { - name: expect.any(String), + name: test.expect.any(String), type: NodeType.Signal, depth: 0, observers: [nodes.memoAId], @@ -193,24 +190,24 @@ describe('collectDependencyGraph', () => { }, } satisfies SerializedDGraph.Graph) - expect(result.graph, 'is JSON-serializable').toMatchObject( + test.expect(result.graph, 'is JSON-serializable').toMatchObject( JSON.parse(JSON.stringify(result.graph)) as any, ) }) - it('listens to visited nodes', () => { + test.it('listens to visited nodes', () => { const captured: NodeID[] = [] - const cb = vi.fn(a => captured.push(a)) + const cb = test.vi.fn(a => captured.push(a)) - createRoot(() => { - const [a, setA] = createSignal(0, {name: 's-a'}) - const [b, setB] = createSignal(0, {name: 's-b'}) - const [c, setC] = createSignal(0, {name: 's-c'}) - const [d, setD] = createSignal(0, {name: 's-d'}) + s.createRoot(() => { + const [a, setA] = s.createSignal(0, {name: 's-a'}) + const [b, setB] = s.createSignal(0, {name: 's-b'}) + const [c, setC] = s.createSignal(0, {name: 's-c'}) + const [d, setD] = s.createSignal(0, {name: 's-d'}) - const mA = createMemo(() => a() + b(), undefined, {name: 'm-a'}) + const mA = s.createMemo(() => a() + b(), undefined, {name: 'm-a'}) - createComputed( + s.createComputed( () => { c() d() @@ -219,7 +216,7 @@ describe('collectDependencyGraph', () => { {name: 'c-a'}, ) - createComputed( + s.createComputed( () => { mA() c() @@ -229,20 +226,20 @@ describe('collectDependencyGraph', () => { ) const nodes = (() => { - const [signalA, signalB, signalC, signalD] = getOwner()!.sourceMap as [ + const [signalA, signalB, signalC, signalD] = Solid$$!.getOwner()!.sourceMap as [ Solid.Signal, Solid.Signal, Solid.Signal, Solid.Signal, ] - const [memoA, computedA, computedB] = getOwner()!.owned as [ + const [memoA, computedA, computedB] = Solid$$!.getOwner()!.owned as [ Solid.Memo, Solid.Computation, Solid.Computation, ] return { - rootId: getSdtId(getOwner()!, ObjectType.Owner), + rootId: getSdtId(Solid$$!.getOwner()!, ObjectType.Owner), signalAId: getSdtId(signalA, ObjectType.Signal), signalBId: getSdtId(signalB, ObjectType.Signal), signalCId: getSdtId(signalC, ObjectType.Signal), @@ -256,7 +253,7 @@ describe('collectDependencyGraph', () => { const result = collectDependencyGraph(nodes.computedB, {onNodeUpdate: cb}) - expect(result.graph).toEqual({ + test.expect(result.graph).toEqual({ [nodes.computedBId]: { name: 'c-b', type: NodeType.Computation, @@ -299,47 +296,47 @@ describe('collectDependencyGraph', () => { }, } satisfies SerializedDGraph.Graph) - expect(cb).toHaveBeenCalledTimes(0) + test.expect(cb).toHaveBeenCalledTimes(0) setA(1) - expect(captured).toHaveLength(3) - expect(captured.includes(nodes.signalAId)).toBeTruthy() - expect(captured.includes(nodes.memoAId)).toBeTruthy() - expect(captured.includes(nodes.computedBId)).toBeTruthy() + test.expect(captured).toHaveLength(3) + test.expect(captured.includes(nodes.signalAId)).toBeTruthy() + test.expect(captured.includes(nodes.memoAId)).toBeTruthy() + test.expect(captured.includes(nodes.computedBId)).toBeTruthy() captured.length = 0 setB(1) - expect(captured).toHaveLength(3) - expect(captured.includes(nodes.signalBId)).toBeTruthy() - expect(captured.includes(nodes.memoAId)).toBeTruthy() - expect(captured.includes(nodes.computedBId)).toBeTruthy() + test.expect(captured).toHaveLength(3) + test.expect(captured.includes(nodes.signalBId)).toBeTruthy() + test.expect(captured.includes(nodes.memoAId)).toBeTruthy() + test.expect(captured.includes(nodes.computedBId)).toBeTruthy() captured.length = 0 setC(1) - expect(captured).toHaveLength(2) - expect(captured.includes(nodes.signalCId)).toBeTruthy() - expect(captured.includes(nodes.computedBId)).toBeTruthy() + test.expect(captured).toHaveLength(2) + test.expect(captured.includes(nodes.signalCId)).toBeTruthy() + test.expect(captured.includes(nodes.computedBId)).toBeTruthy() captured.length = 0 setD(1) - expect(captured.length).toBe(0) + test.expect(captured.length).toBe(0) - batch(() => { + s.batch(() => { setA(2) setB(2) setC(2) }) - expect(captured).toHaveLength(5) - expect(captured.includes(nodes.signalAId)).toBeTruthy() - expect(captured.includes(nodes.signalBId)).toBeTruthy() - expect(captured.includes(nodes.signalCId)).toBeTruthy() - expect(captured.includes(nodes.memoAId)).toBeTruthy() - expect(captured.includes(nodes.computedBId)).toBeTruthy() + test.expect(captured).toHaveLength(5) + test.expect(captured.includes(nodes.signalAId)).toBeTruthy() + test.expect(captured.includes(nodes.signalBId)).toBeTruthy() + test.expect(captured.includes(nodes.signalCId)).toBeTruthy() + test.expect(captured.includes(nodes.memoAId)).toBeTruthy() + test.expect(captured.includes(nodes.computedBId)).toBeTruthy() }) }) }) diff --git a/packages/debugger/src/index.ts b/packages/debugger/src/index.ts index 2f976013..42a1766b 100644 --- a/packages/debugger/src/index.ts +++ b/packages/debugger/src/index.ts @@ -6,7 +6,7 @@ export { observeValueUpdate, removeValueUpdateObserver, } from './main/observe.ts' -export {attachDebugger, createInternalRoot, unobserveAllRoots} from './main/roots.ts' +export {attachDebugger, unobserveAllRoots} from './main/roots.ts' export { getNodeName, getNodeType, diff --git a/packages/debugger/src/inspector/inspector.ts b/packages/debugger/src/inspector/inspector.ts index 5ecc6a39..71d512f6 100644 --- a/packages/debugger/src/inspector/inspector.ts +++ b/packages/debugger/src/inspector/inspector.ts @@ -4,7 +4,6 @@ import {parseLocationString} from '../locator/index.ts' import {NodeType, ValueItemType} from '../main/constants.ts' import {ObjectType, getSdtId} from '../main/id.ts' import {observeValueUpdate, removeValueUpdateObserver} from '../main/observe.ts' -import SolidAPI from '../main/solid-api.ts' import type {Mapped, NodeID, Solid, ValueItemID} from '../main/types.ts' import { getComponentRefreshNode, @@ -15,6 +14,7 @@ import { isSolidSignal, isSolidStore, markOwnerType, + onCleanup, } from '../main/utils.ts' import {encodeValue} from './serialize.ts' import {type InspectorUpdateMap, PropGetterState} from './types.ts' @@ -110,8 +110,8 @@ export class ObservedProps { Object.defineProperty(this.props, key, { get() { const value = get() - if (SolidAPI.getListener()) { - SolidAPI.onCleanup( + if (Solid$$!.getListener()) { + onCleanup( () => --o.n === 0 && self.onPropStateChange?.(key, PropGetterState.Stale), ) } @@ -175,8 +175,8 @@ const $INSPECTOR = Symbol('inspector') const typeToObjectTypeMap = { [NodeType.Signal]: ObjectType.Signal, - [NodeType.Memo]: ObjectType.Owner, - [NodeType.Store]: ObjectType.Store, + [NodeType.Memo]: ObjectType.Owner, + [NodeType.Store]: ObjectType.Store, } function mapSourceValue( @@ -211,7 +211,7 @@ function mapSourceValue( function mapProps(props: Solid.Component['props']) { // proxy props need to be checked for changes in keys - const isProxy = !!(props as any)[SolidAPI.$PROXY] + const isProxy = !!(props as any)[Solid$$!.$PROXY] const record: Mapped.Props['record'] = {} let checkProxyProps: (() => ReturnType) | undefined @@ -318,7 +318,7 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function ( // get location from component.location (typeof location === 'string' && (location = parseLocationString(location))) || // get location from the babel plugin marks - ((location = SolidAPI.getOwnerLocation(owner)) && + ((location = SolidDevtools$$!.getOwnerLocation(owner)) && (location = parseLocationString(location))) ) { details.location = location diff --git a/packages/debugger/src/inspector/serialize.ts b/packages/debugger/src/inspector/serialize.ts index e17ec9dd..60102223 100644 --- a/packages/debugger/src/inspector/serialize.ts +++ b/packages/debugger/src/inspector/serialize.ts @@ -1,6 +1,5 @@ import {type FalsyValue} from '@solid-primitives/utils' import {getSdtId, ObjectType} from '../main/id.ts' -import SolidAPI from '../main/solid-api.ts' import {isStoreNode} from '../main/utils.ts' import { type EncodedValue, @@ -74,7 +73,7 @@ function encode(value: unknown): number { // Store Nodes else if (!ignoreNextStore && isStoreNode(value)) { // might still pass in a proxy - const node = SolidAPI.unwrap(value) + const node = SolidStore$$!.unwrap(value) // set unwrapped as seen as well if (node !== value) Seen.set(node, index) const id = getSdtId(node, ObjectType.StoreNode) diff --git a/packages/debugger/src/inspector/store.ts b/packages/debugger/src/inspector/store.ts index eb678af0..3fa49b5d 100644 --- a/packages/debugger/src/inspector/store.ts +++ b/packages/debugger/src/inspector/store.ts @@ -1,9 +1,6 @@ import {getSdtId, ObjectType} from '../main/id.ts' -import SolidAPI from '../main/solid-api.ts' import type {NodeID, Solid} from '../types.ts' -const {isWrappable} = SolidAPI.STORE_DEV - export type StoreNodeProperty = `${NodeID}:${string}` /** * - `undefined` - property deleted; @@ -25,9 +22,10 @@ export function setOnStoreNodeUpdate(fn: OnNodeUpdate): void { OnNodeUpdate = fn } -// path solid global dev hook -SolidAPI.STORE_DEV.hooks.onStoreNodeUpdate = (node, property, value, prev) => - SolidAPI.untrack(() => { +export function startObservingStores() { + + // path solid global dev hook + SolidStore$$!.hooks.onStoreNodeUpdate = (node, property, value, prev) => { if (!OnNodeUpdate || !Nodes.has(node) || typeof property === 'symbol') return property = property.toString() @@ -39,7 +37,7 @@ SolidAPI.STORE_DEV.hooks.onStoreNodeUpdate = (node, property, value, prev) => if (property === 'length' && typeof value === 'number' && Array.isArray(node)) { return OnNodeUpdate(storeProperty, value) } - isWrappable(prev) && untrackStore(prev, storeProperty) + SolidStore$$!.isWrappable(prev) && untrackStore(prev, storeProperty) // Delete property if (value === undefined) { OnNodeUpdate(storeProperty, undefined) @@ -47,19 +45,18 @@ SolidAPI.STORE_DEV.hooks.onStoreNodeUpdate = (node, property, value, prev) => // Update/Set property else { OnNodeUpdate(storeProperty, {value}) - isWrappable(value) && trackStore(value, storeProperty) + SolidStore$$!.isWrappable(value) && trackStore(value, storeProperty) } - }) + } +} export function observeStoreNode(rootNode: Solid.StoreNode): VoidFunction { // might still pass in a proxy - rootNode = SolidAPI.unwrap(rootNode) + rootNode = SolidStore$$!.unwrap(rootNode) const symbol = Symbol('inspect-store') - return SolidAPI.untrack(() => { - trackStore(rootNode, symbol) - return () => untrackStore(rootNode, symbol) - }) + trackStore(rootNode, symbol) + return () => untrackStore(rootNode, symbol) } function trackStore(node: Solid.StoreNode, parent: ParentProperty): void { @@ -88,12 +85,12 @@ function forEachStoreProp( if (Array.isArray(node)) { for (let i = 0; i < node.length; i++) { const child = node[i] as Solid.StoreNode - isWrappable(child) && fn(i.toString(), child) + SolidStore$$!.isWrappable(child) && fn(i.toString(), child) } } else { for (const key in node) { const {value, get} = Object.getOwnPropertyDescriptor(node, key)! - if (!get && isWrappable(value)) fn(key, value) + if (!get && SolidStore$$!.isWrappable(value)) fn(key, value) } } } diff --git a/packages/debugger/src/inspector/test/index.test.tsx b/packages/debugger/src/inspector/test/index.test.tsx index ec4e97b6..074f67bd 100644 --- a/packages/debugger/src/inspector/test/index.test.tsx +++ b/packages/debugger/src/inspector/test/index.test.tsx @@ -8,12 +8,9 @@ import { } from 'solid-js' import {beforeEach, describe, expect, it, vi} from 'vitest' import {getObjectById, getSdtId, ObjectType} from '../../main/id.ts' -import SolidApi from '../../main/solid-api.ts' import {type Mapped, NodeType, PropGetterState, type Solid, ValueType} from '../../types.ts' import {collectOwnerDetails} from '../inspector.ts' -const {getOwner} = SolidApi - let mockLAST_ID = 0 beforeEach(() => { mockLAST_ID = 0 @@ -32,7 +29,7 @@ describe('collectOwnerDetails', () => { () => { const focused = createMemo( () => { - memo = getOwner()! + memo = Solid$$!.getOwner()! s() createSignal(div, {name: 'element'}) const m = createMemo(() => 0, undefined, {name: 'memo'}) @@ -99,7 +96,7 @@ describe('collectOwnerDetails', () => { children: JSX.Element nested: {foo: number; bar: string} }) => { - owner = getOwner()! + owner = Solid$$!.getOwner()! return
{props.children}
} createRenderEffect(() => ( @@ -153,7 +150,7 @@ describe('collectOwnerDetails', () => { createRoot(dispose => { let owner!: Solid.Owner const Button = (props: JSX.ButtonHTMLAttributes) => { - owner = getOwner()! + owner = Solid$$!.getOwner()! return } createRenderEffect(() => { @@ -210,7 +207,7 @@ describe('collectOwnerDetails', () => { const [count, setCount] = createSignal(0) createMemo(() => { - owner = getOwner()! + owner = Solid$$!.getOwner()! return count() }) @@ -242,7 +239,7 @@ describe('collectOwnerDetails', () => { it('listens to signal updates', () => { createRoot(dispose => { - const owner = getOwner()! + const owner = Solid$$!.getOwner()! const [, setCount] = createSignal(0) // id: "0" const [, setCount2] = createSignal(0) // id: "1" diff --git a/packages/debugger/src/inspector/test/store.test.ts b/packages/debugger/src/inspector/test/store.test.ts index 9f4dd495..a23bd806 100644 --- a/packages/debugger/src/inspector/test/store.test.ts +++ b/packages/debugger/src/inspector/test/store.test.ts @@ -2,15 +2,12 @@ import {createRoot} from 'solid-js' import {createMutable, createStore, modifyMutable, produce, reconcile, unwrap} from 'solid-js/store' import {beforeEach, describe, expect, it, vi} from 'vitest' import {ObjectType, getSdtId} from '../../main/id.ts' -import SolidApi from '../../main/solid-api.ts' import {isSolidStore} from '../../main/utils.ts' import {type Solid} from '../../types.ts' import {type OnNodeUpdate, type StoreNodeProperty, observeStoreNode, setOnStoreNodeUpdate} from '../store.ts' -const {getOwner} = SolidApi - const getOwnerStore = () => { - const owner = getOwner() + const owner = Solid$$!.getOwner() if (!owner) throw new Error('No owner') if (!owner.sourceMap) throw new Error('No sourceMap') const store = owner.sourceMap.find(isSolidStore) diff --git a/packages/debugger/src/locator/index.ts b/packages/debugger/src/locator/index.ts index a3b100f1..c79f3866 100644 --- a/packages/debugger/src/locator/index.ts +++ b/packages/debugger/src/locator/index.ts @@ -17,7 +17,6 @@ import { import type {Debugger} from '../main/index.ts' import * as registry from '../main/component-registry.ts' import {ObjectType, getObjectById} from '../main/id.ts' -import SolidAPI from '../main/solid-api.ts' import {type NodeID} from '../main/types.ts' import {createElementsOverlay} from './element-overlay.tsx' import { @@ -41,6 +40,7 @@ export function createLocator(props: { setLocatorEnabledSignal(signal: Accessor): void onComponentClick(componentId: NodeID, next: VoidFunction): void }) { + const [enabledByPressingSignal, setEnabledByPressingSignal] = createSignal((): boolean => false) props.setLocatorEnabledSignal(createMemo(() => enabledByPressingSignal()())) @@ -165,8 +165,9 @@ export function createLocator(props: { } // Enable the locator when the options were passed by the vite plugin - if (SolidAPI.locatorOptions) { - useLocator(SolidAPI.locatorOptions) + let locator_options = SolidDevtools$$!.get_locator_options() + if (locator_options) { + useLocator(locator_options) } return { diff --git a/packages/debugger/src/main/index.ts b/packages/debugger/src/main/index.ts index c4455806..f0766cb5 100644 --- a/packages/debugger/src/main/index.ts +++ b/packages/debugger/src/main/index.ts @@ -1,18 +1,19 @@ +import {batch, createComputed, createEffect, createMemo, createSignal} from 'solid-js' import {createEventBus, createGlobalEmitter, type GlobalEmitter} from '@solid-primitives/event-bus' import {createStaticStore} from '@solid-primitives/static-store' import {defer} from '@solid-primitives/utils' -import {batch, createComputed, createEffect, createMemo, createSignal} from 'solid-js' +import {error} from '@solid-devtools/shared/utils' import {createDependencyGraph, type DGraphUpdate} from '../dependency/index.ts' import {createInspector, type InspectorUpdate, type ToggleInspectedValueData} from '../inspector/index.ts' import {createLocator} from '../locator/index.ts' -import {type HighlightElementPayload} from '../locator/types.ts' +import {type HighlightElementPayload, type LocatorOptions} from '../locator/types.ts' import {createStructure, type StructureUpdates} from '../structure/index.ts' import {DebuggerModule, DEFAULT_MAIN_VIEW, DevtoolsMainView, TreeWalkerMode} from './constants.ts' import {getObjectById, getSdtId, ObjectType} from './id.ts' -import {createInternalRoot} from './roots.ts' -import SolidApi from './solid-api.ts' import {type Mapped, type NodeID} from './types.ts' import {createBatchedUpdateEmitter} from './utils.ts' +import {startObserve} from './observe.ts' +import {startObservingStores} from '../inspector/store.ts' export namespace Debugger { export type InspectedState = { @@ -53,7 +54,17 @@ export type DebuggerEmitter = { input: GlobalEmitter } -const plugin = createInternalRoot(() => { +let debugger_instance: undefined | ReturnType + +function createDebugger() { + + if (!globalThis.SolidDevtools$$) { + error(`Debugger hasn't found the exposed Solid Devtools API. Did you import the setup script?`) + } + + startObserve() + startObservingStores() + const hub: DebuggerEmitter = { output: createGlobalEmitter(), input: createGlobalEmitter(), @@ -259,26 +270,28 @@ const plugin = createInternalRoot(() => { } }) - /** - * Used for connecting debugger to devtools - */ - function useDebugger() { - return { - meta: { - versions: SolidApi.versions, - }, - enabled: debuggerEnabled, - toggleEnabled: (enabled: boolean) => void toggleModules('debugger', enabled), - on: hub.output.on, - listen: hub.output.listen, - emit: hub.input.emit, - } - } - return { - useDebugger, - useLocator: locator.useLocator, + meta: { + versions: SolidDevtools$$!.versions, + }, + enabled: debuggerEnabled, + toggleEnabled: (enabled: boolean) => void toggleModules('debugger', enabled), + on: hub.output.on, + listen: hub.output.listen, + emit: hub.input.emit, + locator, } -}) +} -export const {useDebugger, useLocator} = plugin + +/** + * Used for connecting debugger to devtools + */ +export function useDebugger() { + debugger_instance ??= createDebugger() + return debugger_instance +} + +export function useLocator(options: LocatorOptions) { + return useDebugger().locator.useLocator(options) +} diff --git a/packages/debugger/src/main/observe.ts b/packages/debugger/src/main/observe.ts index 9794421b..bc39e506 100644 --- a/packages/debugger/src/main/observe.ts +++ b/packages/debugger/src/main/observe.ts @@ -6,31 +6,26 @@ Dev hooks and observing reactive graph nodes import {chain, tryOnCleanup} from '@solid-primitives/utils' import {attachDebugger} from './roots.ts' -import SolidAPI from './solid-api.ts' import {type Solid, type ValueUpdateListener} from './types.ts' import {isSolidRoot} from './utils.ts' -for (const e of SolidAPI.getDevEvents()) { - attachDebugger(e.data) -} -// -// AFTER CREATE OWNER -// +const GraphUpdateListeners = new Set() + +export function startObserve() { -SolidAPI.DEV.hooks.afterCreateOwner = function (owner) { - if (isSolidRoot(owner)) { + for (const owner of SolidDevtools$$!.get_created_owners()) { attachDebugger(owner) } -} -// -// AFTER UPDATE -// - -const GraphUpdateListeners = new Set() + Solid$$!.hooks.afterCreateOwner = function (owner) { + if (isSolidRoot(owner)) { + attachDebugger(owner) + } + } -SolidAPI.DEV.hooks.afterUpdate = chain(GraphUpdateListeners) + Solid$$!.hooks.afterUpdate = chain(GraphUpdateListeners) +} /** * Runs the callback on every Solid Graph Update – whenever computations update because of a signal change. diff --git a/packages/debugger/src/main/roots.ts b/packages/debugger/src/main/roots.ts index 9c54091a..a78ec8f2 100644 --- a/packages/debugger/src/main/roots.ts +++ b/packages/debugger/src/main/roots.ts @@ -2,7 +2,6 @@ import {warn} from '@solid-devtools/shared/utils' import {clearComponentRegistry} from './component-registry.ts' import {NodeType} from './constants.ts' import {ObjectType, getSdtId} from './id.ts' -import SolidAPI from './solid-api.ts' import {type NodeID, type Solid} from './types.ts' import {isSolidRoot, onOwnerCleanup} from './utils.ts' @@ -62,8 +61,6 @@ function changeRootAttachment(root: Solid.Root, newParent: Solid.Owner | null): } } -let InternalRootCount = 0 - /** * Helps the debugger find and reattach an reactive owner created by `createRoot` to it's detached parent. * @@ -76,8 +73,7 @@ let InternalRootCount = 0 * attachDebugger(); * }); */ -export function attachDebugger(owner = SolidAPI.getOwner()): void { - if (InternalRootCount) return +export function attachDebugger(owner = Solid$$!.getOwner()): void { if (!owner) return warn('reatachOwner helper should be called synchronously in a reactive owner.') @@ -146,19 +142,6 @@ export function unobserveAllRoots(): void { clearComponentRegistry() } -/** - * Sold's `createRoot` primitive that won't be tracked by the debugger. - */ -export const createInternalRoot: typeof SolidAPI.createRoot = (fn, detachedOwner) => { - InternalRootCount++ - const r = SolidAPI.createRoot(dispose => { - ;(SolidAPI.getOwner() as Solid.Root).isInternal = true - return fn(dispose) - }, detachedOwner) - InternalRootCount-- - return r -} - /** * Finds the top-level root owner of a given owner. */ diff --git a/packages/debugger/src/main/solid-api.ts b/packages/debugger/src/main/solid-api.ts deleted file mode 100644 index c13bc084..00000000 --- a/packages/debugger/src/main/solid-api.ts +++ /dev/null @@ -1,9 +0,0 @@ -if (!globalThis.SolidDevtools$$) { - throw new Error( - `[solid-devtools]: Debugger hasn't found the exposed Solid Devtools API. Did you import the setup script?`, - ) -} - -const SolidApi = globalThis.SolidDevtools$$ - -export default SolidApi diff --git a/packages/debugger/src/main/test/update.test.ts b/packages/debugger/src/main/test/update.test.ts index 2326bb2a..9104514f 100644 --- a/packages/debugger/src/main/test/update.test.ts +++ b/packages/debugger/src/main/test/update.test.ts @@ -6,9 +6,6 @@ import { observeValueUpdate, removeValueUpdateObserver, } from '../observe.ts' -import solidApi from '../solid-api.ts' - -const {getOwner} = solidApi describe('addSolidUpdateListener', () => { it('listens to solid updates', () => @@ -50,7 +47,7 @@ describe('interceptComputationRerun', () => { return last_value }, 'init') - const owner = getOwner()!.owned![0]! + const owner = Solid$$!.getOwner()!.owned![0]! interceptComputationRerun(owner, (fn, prev) => { last_patched_prev = prev last_patched_value = fn() @@ -74,7 +71,7 @@ describe('observeValueUpdate', () => { it('patches signal', () => createRoot(dispose => { const [, setCount] = createSignal(0, {name: 's1'}) - const signal = getOwner()!.sourceMap![0]! + const signal = Solid$$!.getOwner()!.sourceMap![0]! const symbol = Symbol() let last_prev: unknown let last_value: unknown diff --git a/packages/debugger/src/main/test/utils.test.ts b/packages/debugger/src/main/test/utils.test.ts index e0c4250d..972c8dbe 100644 --- a/packages/debugger/src/main/test/utils.test.ts +++ b/packages/debugger/src/main/test/utils.test.ts @@ -1,41 +1,40 @@ import * as s from 'solid-js' import * as vi from 'vitest' import {NodeType} from '../constants.ts' -import api from '../solid-api.ts' import {type Solid} from '../types.ts' -import {getOwnerType} from '../utils.ts' +import {getOwnerType, onCleanup} from '../utils.ts' vi.describe('getOwnerType', () => { const tests = { Component: () => { let owner!: Solid.Owner s.createComponent(() => { - owner = api.getOwner()! + owner = Solid$$!.getOwner()! return '' }, {}) vi.expect(getOwnerType(owner)).toBe(NodeType.Component) }, Effect: () => { s.createEffect(() => { - vi.expect(getOwnerType(api.getOwner()!)).toBe(NodeType.Effect) + vi.expect(getOwnerType(Solid$$!.getOwner()!)).toBe(NodeType.Effect) }) }, Memo: () => { - s.createMemo(() => vi.expect(getOwnerType(api.getOwner()!)).toBe(NodeType.Memo)) + s.createMemo(() => vi.expect(getOwnerType(Solid$$!.getOwner()!)).toBe(NodeType.Memo)) }, Computation: () => { s.createComputed(() => - vi.expect(getOwnerType(api.getOwner()!)).toBe(NodeType.Computation), + vi.expect(getOwnerType(Solid$$!.getOwner()!)).toBe(NodeType.Computation), ) }, Render: () => { s.createRenderEffect(() => - vi.expect(getOwnerType(api.getOwner()!)).toBe(NodeType.Render), + vi.expect(getOwnerType(Solid$$!.getOwner()!)).toBe(NodeType.Render), ) }, Root: () => { s.createRoot(dispose => { - vi.expect(getOwnerType(api.getOwner()!)).toBe(NodeType.Root) + vi.expect(getOwnerType(Solid$$!.getOwner()!)).toBe(NodeType.Root) dispose() }) }, @@ -45,7 +44,7 @@ vi.describe('getOwnerType', () => { Ctx.Provider({ value: 1, get children() { - memo = api.getOwner()! + memo = Solid$$!.getOwner()! return '' }, }) @@ -65,7 +64,7 @@ vi.describe('getOwnerType', () => { dispose = d cb() }) - api.onCleanup(dispose) + onCleanup(dispose) }, memo: s.createMemo, effect: s.createEffect, diff --git a/packages/debugger/src/main/types.ts b/packages/debugger/src/main/types.ts index 73ce51e4..ad00920a 100644 --- a/packages/debugger/src/main/types.ts +++ b/packages/debugger/src/main/types.ts @@ -1,60 +1,11 @@ -import type * as SolidAPI from 'solid-js' -import type {$PROXY, DEV, getListener, getOwner, onCleanup, untrack} from 'solid-js' -import type * as StoreAPI from 'solid-js/store' -import type {DEV as STORE_DEV, unwrap} from 'solid-js/store' -import type * as WebAPI from 'solid-js/web' import type {EncodedValue, PropGetterState} from '../inspector/types.ts' -import type {LocatorOptions, SourceLocation} from '../locator/types.ts' +import type {SourceLocation} from '../locator/types.ts' import {NodeType, ValueItemType} from './constants.ts' // // EXPOSED SOLID API // -export const enum DevEventType { - RootCreated = 'RootCreated', -} - -export type DevEventDataMap = { - [DevEventType.RootCreated]: Solid.Owner -} - -export type StoredDevEvent = { - [K in keyof DevEventDataMap]: { - timestamp: number - type: K - data: DevEventDataMap[K] - } -}[keyof DevEventDataMap] - -declare global { - /** Solid DEV APIs exposed to the debugger by the setup script */ - var SolidDevtools$$: - | { - readonly Solid: typeof SolidAPI - readonly Store: typeof StoreAPI - readonly Web: typeof WebAPI - readonly DEV: NonNullable - readonly getOwner: typeof getOwner - readonly createRoot: typeof SolidAPI.createRoot - readonly getListener: typeof getListener - readonly onCleanup: typeof onCleanup - readonly $PROXY: typeof $PROXY - readonly untrack: typeof untrack - readonly STORE_DEV: NonNullable - readonly unwrap: typeof unwrap - readonly getDevEvents: () => StoredDevEvent[] - readonly locatorOptions: LocatorOptions | null - readonly versions: { - readonly client: string | null - readonly solid: string | null - readonly expectedSolid: string | null - } - readonly getOwnerLocation: (owner: Solid.Owner) => string | null - } - | undefined -} - // Additional "#" character is added to distinguish NodeID from string export type NodeID = `#${string}` diff --git a/packages/debugger/src/main/utils.ts b/packages/debugger/src/main/utils.ts index c45a123c..08871c05 100644 --- a/packages/debugger/src/main/utils.ts +++ b/packages/debugger/src/main/utils.ts @@ -1,13 +1,9 @@ import {trimString} from '@solid-devtools/shared/utils' import {type Emit} from '@solid-primitives/event-bus' import {throttle} from '@solid-primitives/scheduled' -import {$PROXY} from 'solid-js' import {NodeType} from './constants.ts' -import SolidAPI from './solid-api.ts' import {type Solid} from './types.ts' -const $NODE = SolidAPI.STORE_DEV.$NODE - export const isObject = (o: unknown): o is object => typeof o === 'object' && !!o export const isSolidOwner = ( @@ -27,11 +23,11 @@ export const isSolidMemo = (o: Readonly): o is Solid.Memo => export const isSolidComponent = (o: Readonly): o is Solid.Component => 'component' in o -export const isStoreNode = (o: object): o is Solid.StoreNode => $NODE in o +export const isStoreNode = (o: object): o is Solid.StoreNode => SolidStore$$!.$NODE in o export const isSolidStore = ( o: Solid.Owner | Solid.SourceMapValue | Solid.Store, -): o is Solid.Store => !('observers' in o) && 'value' in o && isObject(o.value) && $PROXY in o.value +): o is Solid.Store => !('observers' in o) && 'value' in o && isObject(o.value) && Solid$$!.$PROXY in o.value export const isSolidSignal = (o: Solid.SourceMapValue): o is Solid.Signal => 'value' in o && 'observers' in o && 'observerSlots' in o && 'comparator' in o @@ -97,6 +93,14 @@ export function isDisposed(o: Readonly): boolean { : (o as Solid.Root).isDisposed) } +export function onCleanup(fn: () => void) { + let owner = Solid$$!.getOwner() + if (owner) { + if (owner.cleanups === null) owner.cleanups = [fn] + else owner.cleanups.push(fn) + } +} + export function getComponentRefreshNode(owner: Readonly): Solid.Memo | null { const {owned} = owner let refresh: Solid.Owner diff --git a/packages/debugger/src/setup.ts b/packages/debugger/src/setup.ts index 0e26f367..734a8fa4 100644 --- a/packages/debugger/src/setup.ts +++ b/packages/debugger/src/setup.ts @@ -6,14 +6,9 @@ It also starts listening to Solid DEV events and stores them to be sent to the d */ -import {error} from '@solid-devtools/shared/utils' -import * as SolidAPI from 'solid-js' -import {$PROXY, DEV, createRoot, getListener, getOwner, onCleanup, untrack} from 'solid-js' -import * as StoreAPI from 'solid-js/store' -import {DEV as STORE_DEV, unwrap} from 'solid-js/store' -import * as WebAPI from 'solid-js/web' +import {error, warn} from '@solid-devtools/shared/utils' import type {LocatorOptions} from './locator/types.ts' -import {DevEventType, type Solid, type StoredDevEvent} from './main/types.ts' +import type {Solid} from './main/types.ts' const OwnerLocationMap = new WeakMap() @@ -22,7 +17,7 @@ const OwnerLocationMap = new WeakMap() * Used by the babel plugin. */ export function setOwnerLocation(location: string) { - const owner = getOwner() + const owner = Solid$$!.getOwner() owner && OwnerLocationMap.set(owner, location) } @@ -35,69 +30,72 @@ export function useLocator(options: LocatorOptions) { PassedLocatorOptions = options } -let ClientVersion: string | null = null -let SolidVersion: string | null = null +let ClientVersion: string | null = null let ExpectedSolidVersion: string | null = null export function setClientVersion(version: string) { ClientVersion = version } - -export function setSolidVersion(version: string, expected: string) { - SolidVersion = version +export function setExpectedSolidVersion(expected: string) { ExpectedSolidVersion = expected } +export const setSolidVersion = setExpectedSolidVersion // back compat 0.24.1 -let DevEvents: StoredDevEvent[] | null = [] - -if (window.SolidDevtools$$) { - error('Debugger is already setup') +declare global { + /** Solid DEV APIs exposed to the debugger by the setup script */ + var SolidDevtools$$: undefined | { + get_created_owners(): Solid.Owner[] + get_locator_options(): LocatorOptions | null + versions: { + get_client(): string | null + get_solid(): string | null + get_expected_solid(): string | null + } + getOwnerLocation: (owner: Solid.Owner) => string | null + } } -if (!DEV || !STORE_DEV) { - error('SolidJS in not in development mode!') -} else { - window.SolidDevtools$$ = { - Solid: SolidAPI, - Store: StoreAPI, - Web: WebAPI, - DEV, - getOwner, - createRoot, - getListener, - onCleanup, - $PROXY, - untrack, - STORE_DEV, - unwrap, - getDevEvents() { - const events = DevEvents ?? [] - DevEvents = null - return events - }, - get locatorOptions() { - return PassedLocatorOptions - }, - versions: { - get client() { - return ClientVersion +export function setupSolidDevtools( + Solid$$: typeof globalThis.Solid$$, + SolidStore$$: typeof globalThis.SolidStore$$, +) { + + if (globalThis.SolidDevtools$$) { + warn('Debugger is already setup') + return + } + + if (!globalThis.Solid$$) { + error('SolidJS in not in development mode!') + } else { + + let created_owners: Solid.Owner[] | null = [] + + globalThis.SolidDevtools$$ = { + get_created_owners() { + let owners = created_owners ?? [] + created_owners = null + return owners }, - get solid() { - return SolidVersion + get_locator_options() { + return PassedLocatorOptions }, - get expectedSolid() { - return ExpectedSolidVersion + versions: { + get_client() { + return ClientVersion + }, + get_solid() { + return Solid$$!.version + }, + get_expected_solid() { + return ExpectedSolidVersion + }, }, - }, - getOwnerLocation, - } + getOwnerLocation, + } - DEV.hooks.afterCreateOwner = function (owner) { - if (!DevEvents) return - DevEvents.push({ - timestamp: Date.now(), - type: DevEventType.RootCreated, - data: owner, - }) + Solid$$!.hooks.afterCreateOwner = owner => { + created_owners?.push(owner) + } } } diff --git a/packages/debugger/src/structure/test/walker.test.tsx b/packages/debugger/src/structure/test/walker.test.tsx index 059b484c..0d277ddd 100644 --- a/packages/debugger/src/structure/test/walker.test.tsx +++ b/packages/debugger/src/structure/test/walker.test.tsx @@ -9,13 +9,10 @@ import { import {beforeEach, describe, expect, it, vi} from 'vitest' import {NodeType, TreeWalkerMode} from '../../main/constants.ts' import {$setSdtId, ObjectType, getSdtId} from '../../main/id.ts' -import SolidApi from '../../main/solid-api.ts' import {type Mapped, type Solid} from '../../main/types.ts' import {getNodeName} from '../../main/utils.ts' import {type ComputationUpdateHandler, walkSolidTree} from '../walker.ts' -const {getOwner} = SolidApi - let mockLAST_ID = 0 beforeEach(() => { mockLAST_ID = 0 @@ -42,7 +39,7 @@ describe('TreeWalkerMode.Owners', () => { { const [dispose, owner] = createRoot(_dispose => { mockTree() - return [_dispose, getOwner()! as Solid.Root] + return [_dispose, Solid$$!.getOwner()! as Solid.Root] }) const tree = walkSolidTree(owner, { @@ -113,7 +110,7 @@ describe('TreeWalkerMode.Owners', () => { {name: 'WRAPPER'}, ) - const rootOwner = getOwner()! as Solid.Root + const rootOwner = Solid$$!.getOwner()! as Solid.Root const tree = walkSolidTree(rootOwner, { rootId: $setSdtId(rootOwner, '#0'), onComputationUpdate: () => { @@ -172,11 +169,11 @@ describe('TreeWalkerMode.Owners', () => { let computedOwner!: Solid.Owner const [a, setA] = createSignal(0) createComputed(() => { - computedOwner = getOwner()! + computedOwner = Solid$$!.getOwner()! a() }) - const owner = getOwner()! as Solid.Root + const owner = Solid$$!.getOwner()! as Solid.Root walkSolidTree(owner, { onComputationUpdate: (...args) => capturedComputationUpdates.push(args), rootId: $setSdtId(owner, '#ff'), @@ -222,7 +219,7 @@ describe('TreeWalkerMode.Owners', () => { ) }) - const owner = getOwner()! as Solid.Root + const owner = Solid$$!.getOwner()! as Solid.Root const components: string[] = [] @@ -268,7 +265,7 @@ describe('TreeWalkerMode.Components', () => { const [a, set] = createSignal(0) createComputed(a) toTrigger.push(() => set(1)) - testComponents.push(getOwner()! as Solid.Component) + testComponents.push(Solid$$!.getOwner()! as Solid.Component) return createRoot(_ => (
{props.n === 0 ? 'end' : }
)) @@ -288,7 +285,7 @@ describe('TreeWalkerMode.Components', () => { ) }) - const owner = getOwner()! as Solid.Root + const owner = Solid$$!.getOwner()! as Solid.Root const computationUpdates: Parameters[] = [] @@ -374,7 +371,7 @@ describe('TreeWalkerMode.DOM', () => { const [a, set] = createSignal(0) createComputed(a) toTrigger.push(() => set(1)) - testComponents.push(getOwner()! as Solid.Component) + testComponents.push(Solid$$!.getOwner()! as Solid.Component) return createRoot(_ => (
{props.n === 0 ? 'end' : }
)) @@ -397,7 +394,7 @@ describe('TreeWalkerMode.DOM', () => { } createRenderEffect(() => ) - const owner = getOwner()! as Solid.Root + const owner = Solid$$!.getOwner()! as Solid.Root const computationUpdates: Parameters[] = [] diff --git a/packages/debugger/test_setup.ts b/packages/debugger/test_setup.ts new file mode 100644 index 00000000..84ebd788 --- /dev/null +++ b/packages/debugger/test_setup.ts @@ -0,0 +1,11 @@ +/* Run setup before importing the debugger */ +import * as solid from 'solid-js' +import * as store from 'solid-js/store' +import {setupSolidDevtools} from './src/setup.ts' +setupSolidDevtools(solid.DEV, store.DEV) + +import {startObserve} from './src/main/observe.ts' +startObserve() + +import {startObservingStores} from './src/inspector/store.ts' +startObservingStores() diff --git a/packages/debugger/tsconfig.json b/packages/debugger/tsconfig.json index 21c1c5be..c9f45645 100644 --- a/packages/debugger/tsconfig.json +++ b/packages/debugger/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + }, "include": ["src/**/*", "test/**/*"] } diff --git a/packages/debugger/vite.config.ts b/packages/debugger/vite.config.ts index f3340dda..80e2c725 100644 --- a/packages/debugger/vite.config.ts +++ b/packages/debugger/vite.config.ts @@ -1,6 +1,6 @@ -import {vitestFullConfig} from '../../configs/vitest.config' +import {vitestFullConfig} from '../../configs/vitest.config.ts' export default vitestFullConfig(c => { // setup needs to run before the tests to add solid-js api to the global scope - c.test!.setupFiles = './src/setup.ts' + c.test!.setupFiles = 'test_setup.ts' }) diff --git a/packages/extension/content/debugger.ts b/packages/extension/content/debugger.ts index b935d0ab..cef8aeeb 100644 --- a/packages/extension/content/debugger.ts +++ b/packages/extension/content/debugger.ts @@ -78,12 +78,15 @@ Expected version: ${title}@${expected_str}`) const debug = useDebugger() /* Check versions */ -warn_on_version_mismatch(debug.meta.versions.client, import.meta.env.EXPECTED_CLIENT, 'solid-devtools') -warn_on_version_mismatch(debug.meta.versions.solid, debug.meta.versions.expectedSolid, 'solid-js') +warn_on_version_mismatch(debug.meta.versions.get_client(), import.meta.env.EXPECTED_CLIENT, 'solid-devtools') +warn_on_version_mismatch(debug.meta.versions.get_solid(), debug.meta.versions.get_expected_solid(), 'solid-js') // in case of navigation/page reload, reset the locator mode state in the extension toContent('ResetPanel') -toContent('Debugger_Connected', debug.meta.versions) +toContent('Debugger_Connected', { + client: debug.meta.versions.get_client(), + solid: debug.meta.versions.get_solid(), +}) fromContent('DevtoolsOpened', () => debug.toggleEnabled(true)) fromContent('DevtoolsClosed', () => debug.toggleEnabled(false)) diff --git a/packages/extension/package.json b/packages/extension/package.json index bfcaa54c..027e3bc7 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -23,8 +23,7 @@ "@solid-devtools/frontend": "workspace:^", "@solid-devtools/shared": "workspace:^", "@solid-primitives/utils": "^6.2.3", - "solid-devtools": "workspace:^", - "solid-js": "^1.9.3" + "solid-devtools": "workspace:^" }, "overrides": { "@crxjs/vite-plugin": { diff --git a/packages/extension/tsconfig.json b/packages/extension/tsconfig.json index 32779884..384a1eeb 100644 --- a/packages/extension/tsconfig.json +++ b/packages/extension/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "outDir": "dist", "types": ["vite/client", "@types/chrome"], - "paths": { - "@solid-devtools/shared/*": ["../shared/src/*"] - } }, "include": [ "panel/**/*", diff --git a/packages/extension/vite.config.ts b/packages/extension/vite.config.ts index c78cf5da..ba8ce27f 100644 --- a/packages/extension/vite.config.ts +++ b/packages/extension/vite.config.ts @@ -1,7 +1,6 @@ import * as crx from '@crxjs/vite-plugin' import * as fs from 'node:fs' import * as module from 'node:module' -import * as path from 'node:path' import * as assert from 'node:assert' import * as vite from 'vite' import solid from 'vite-plugin-solid' @@ -9,7 +8,6 @@ import ext_pkg from './package.json' import {icons} from './shared/icons.js' const require = module.createRequire(import.meta.url) -const cwd = process.cwd() const browser = process.env['BROWSER'] ?? 'chrome' assert.ok(browser === 'chrome' || browser === 'firefox') @@ -89,11 +87,7 @@ const sdt_version = JSON.stringify(sdt_pkg.version.match(/\d+.\d+.\d+/)![0]) const vite_config: vite.UserConfig = { server: {port: 3333}, resolve: { - alias: { - 'solid-js/web': path.resolve(cwd, 'node_modules/solid-js/web/dist/web.js'), - 'solid-js/store': path.resolve(cwd, 'node_modules/solid-js/store/dist/store.js'), - 'solid-js': path.resolve(cwd, 'node_modules/solid-js/dist/solid.js'), - }, + conditions: ['browser'] }, plugins: [ solid({dev: false, hot: false}), diff --git a/packages/frontend/build.ts b/packages/frontend/build.ts index 80d731f4..e75d1120 100644 --- a/packages/frontend/build.ts +++ b/packages/frontend/build.ts @@ -75,12 +75,11 @@ async function main() { external: external, }] - - const ts_entries = [ - entry_index_filename, - ] - - await build.build(esb_options, ts_entries, is_dev) + + await build.build(esb_options, + is_dev, + dirname, + dist_dirname) } diff --git a/packages/frontend/package.json b/packages/frontend/package.json index b7841014..8645c1b2 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -59,9 +59,6 @@ "clsx": "^2.1.1", "solid-headless": "^0.13.1" }, - "devDependencies": { - "solid-js": "^1.9.3" - }, "peerDependencies": { "solid-js": "^1.9.0" } diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index da068305..57b3c5c5 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -1,5 +1,7 @@ { "extends": "../../tsconfig.json", - "compilerOptions": {}, + "compilerOptions": { + "outDir": "dist" + }, "include": ["src/**/*"] } diff --git a/packages/logger/build.ts b/packages/logger/build.ts index 0cdb2139..02c1ef0d 100644 --- a/packages/logger/build.ts +++ b/packages/logger/build.ts @@ -33,10 +33,11 @@ async function main() { entryPoints: entries, external: external, }] - - const ts_entries = entries - - await build.build(esb_options, ts_entries, is_dev) + + await build.build(esb_options, + is_dev, + dirname, + dist_dirname) } diff --git a/packages/logger/package.json b/packages/logger/package.json index 15dfd9d1..9fdd291e 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -47,9 +47,6 @@ "test:unit": "echo \"NOOP test\"", "test:types": "tsc --noEmit --paths null" }, - "devDependencies": { - "solid-js": "^1.9.3" - }, "dependencies": { "@nothing-but/utils": "~0.17.0", "@solid-devtools/debugger": "workspace:^", diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json index 21c1c5be..6743eb34 100644 --- a/packages/logger/tsconfig.json +++ b/packages/logger/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, "include": ["src/**/*", "test/**/*"] } diff --git a/packages/main/build.ts b/packages/main/build.ts index afe188e5..8d54cf9f 100644 --- a/packages/main/build.ts +++ b/packages/main/build.ts @@ -1,11 +1,10 @@ -import * as path from 'node:path' -import * as url from 'node:url' -import * as esb from 'esbuild' +import * as path from 'node:path' +import * as url from 'node:url' +import * as esb from 'esbuild' -import pkg from './package.json' with {type: 'json'} -import solid_pkg from 'solid-js/package.json' with {type: 'json'} +import pkg from './package.json' with {type: 'json'} -import * as build from '../../build.ts' +import * as build from '../../build.ts' const filename = url.fileURLToPath(import.meta.url) @@ -42,7 +41,6 @@ async function main() { external: external, define: { 'process.env.CLIENT_VERSION': JSON.stringify(pkg.version), - 'process.env.SOLID_VERSION': JSON.stringify(solid_pkg.version), 'process.env.EXPECTED_SOLID_VERSION': JSON.stringify(pkg.peerDependencies['solid-js'].match(/\d+.\d+.\d+/)![0]), }, }, { @@ -56,14 +54,10 @@ async function main() { platform: 'node', }] - const ts_entries = [ - entry_index_filename, - entry_setup_filename, - entry_vite_filename, - entry_babel_filename, - ] - - await build.build(esb_options, ts_entries, is_dev) + await build.build(esb_options, + is_dev, + dirname, + dist_dirname) } diff --git a/packages/main/src/env.d.ts b/packages/main/src/env.d.ts index ba582a05..c52fc17b 100644 --- a/packages/main/src/env.d.ts +++ b/packages/main/src/env.d.ts @@ -3,7 +3,6 @@ declare global { namespace NodeJS { interface ProcessEnv { CLIENT_VERSION: string - SOLID_VERSION: string EXPECTED_SOLID_VERSION: string } } diff --git a/packages/main/src/setup.ts b/packages/main/src/setup.ts index 7465f9ff..7975e9bc 100644 --- a/packages/main/src/setup.ts +++ b/packages/main/src/setup.ts @@ -1,14 +1,17 @@ -import '@solid-devtools/debugger/setup' - +import * as solid from 'solid-js' +import * as store from 'solid-js/store' import { setClientVersion, setOwnerLocation, - setSolidVersion, + setExpectedSolidVersion, useLocator, + setupSolidDevtools, } from '@solid-devtools/debugger/setup' +setupSolidDevtools(solid.DEV, store.DEV) + setClientVersion(process.env.CLIENT_VERSION) -setSolidVersion(process.env.SOLID_VERSION, process.env.EXPECTED_SOLID_VERSION) +setExpectedSolidVersion(process.env.EXPECTED_SOLID_VERSION) /** * Set debugger locator module options. diff --git a/packages/main/tsconfig.json b/packages/main/tsconfig.json index 1047f4c7..19a95c02 100644 --- a/packages/main/tsconfig.json +++ b/packages/main/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "outDir": "dist", "types": ["@total-typescript/ts-reset", "node"] }, "include": ["src/**/*", "test/**/*"] diff --git a/packages/overlay/build.ts b/packages/overlay/build.ts index b428dad7..3fc91e5d 100644 --- a/packages/overlay/build.ts +++ b/packages/overlay/build.ts @@ -48,14 +48,12 @@ async function main() { '@nothing-but/utils', ] - const entries = [ - entry_index_filename, - entry_index_noop_filename, - ] - const esb_options: esb.BuildOptions[] = [{ ...common, - entryPoints: entries, + entryPoints: [ + entry_index_filename, + entry_index_noop_filename, + ], external: external, loader: {'.css': 'text'}, plugins: [ @@ -64,9 +62,10 @@ async function main() { ], }] - const ts_entries = entries - - await build.build(esb_options, ts_entries, is_dev) + await build.build(esb_options, + is_dev, + dirname, + dist_dirname) } diff --git a/packages/overlay/package.json b/packages/overlay/package.json index 601407e9..d2484dbc 100644 --- a/packages/overlay/package.json +++ b/packages/overlay/package.json @@ -28,17 +28,17 @@ "type": "module", "main": "./dist/index_noop.js", "module": "./dist/index_noop.js", - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "exports": { "browser": { "development": { - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" }, - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/index_noop.js" }, - "types": "./dist/types/index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/index_noop.js" }, "scripts": { @@ -58,8 +58,5 @@ }, "peerDependencies": { "solid-js": "^1.9.0" - }, - "devDependencies": { - "solid-js": "^1.9.3" } } diff --git a/packages/overlay/src/index.tsx b/packages/overlay/src/index.tsx index e31f767c..97b3ed31 100644 --- a/packages/overlay/src/index.tsx +++ b/packages/overlay/src/index.tsx @@ -1,20 +1,29 @@ -import '@solid-devtools/debugger/setup' +/* Run setup before importing the debugger */ +import * as s from 'solid-js' +import * as store from 'solid-js/store' +import * as web from 'solid-js/web' +import {setupSolidDevtools} from '@solid-devtools/debugger/setup' +setupSolidDevtools(s.DEV, store.DEV) -import {num} from '@nothing-but/utils' +import * as num from '@nothing-but/utils/num' import {useDebugger} from '@solid-devtools/debugger/bundled' import {Icon, MountIcons} from '@solid-devtools/frontend' -import {useIsMobile, useIsTouch} from '@solid-devtools/shared/primitives' +import {useIsMobile, useIsTouch, atom} from '@solid-devtools/shared/primitives' import {createBodyCursor} from '@solid-primitives/cursor' import {makeEventListener} from '@solid-primitives/event-listener' -import * as s from 'solid-js' -import {Dynamic, Portal} from 'solid-js/web' import {Devtools} from './controller.tsx' import frontendStyles from '@solid-devtools/frontend/dist/styles.css' import overlayStyles from './styles.css' -export function attachDevtoolsOverlay(props: s.ComponentProps = {}): VoidFunction { - let dispose: VoidFunction | undefined +export type OverlayOptions = { + defaultOpen?: boolean + alwaysOpen?: boolean + noPadding?: boolean +} + +export function attachDevtoolsOverlay(props?: OverlayOptions): (() => void) { + let dispose: (() => void) | undefined setTimeout(() => { s.createRoot(_dispose => { @@ -28,55 +37,54 @@ export function attachDevtoolsOverlay(props: s.ComponentProps = } } -const Overlay: s.Component<{ - defaultOpen?: boolean - alwaysOpen?: boolean - noPadding?: boolean -}> = ({defaultOpen, alwaysOpen, noPadding}) => { +const Overlay: s.Component = ({defaultOpen, alwaysOpen, noPadding}) => { + const debug = useDebugger() - if (defaultOpen || alwaysOpen) debug.toggleEnabled(true) - const [isOpen, _setOpen] = s.createSignal(alwaysOpen || debug.enabled()) - const setOpen = alwaysOpen - ? () => { - /**/ - } - : (enabled: boolean) => { - s.batch(() => { - debug.toggleEnabled(enabled) - _setOpen(enabled) - }) - } + + if (defaultOpen || alwaysOpen) { + debug.toggleEnabled(true) + } + + const isOpen = atom(alwaysOpen || debug.enabled()) + function toggleOpen(enabled?: boolean) { + if (!alwaysOpen) { + enabled ??= !isOpen() + debug.toggleEnabled(enabled) + isOpen.set(enabled) + } + } const isMobile = useIsMobile() - const isTouch = useIsTouch() + const isTouch = useIsTouch() - const [progress, setProgress] = s.createSignal(0.5) - const [dragging, setDragging] = s.createSignal(false) - s.createComputed(() => setProgress(isMobile() ? 0.8 : 0.5)) + const progress = s.createMemo( + () => atom(isMobile() ? 0.8 : 0.5) + ) + const dragging = atom(false) makeEventListener(window, 'pointermove', e => { if (!dragging()) return const vh = window.innerHeight - setProgress(1 - num.clamp(e.y, 0, vh - 300) / vh) + progress().set(1 - num.clamp(e.y, 0, vh - 300) / vh) }) - makeEventListener(window, 'pointerup', setDragging.bind(void 0, false)) + makeEventListener(window, 'pointerup', () => dragging.set(false)) createBodyCursor(() => dragging() && 'row-resize') return ( - +
{!alwaysOpen && ( -