diff options
-rw-r--r-- | .changeset/wet-schools-dream.md | 14 | ||||
-rw-r--r-- | docs/src/config.ts | 1 | ||||
-rw-r--r-- | docs/src/pages/guides/debugging.md | 6 | ||||
-rw-r--r-- | docs/src/pages/reference/builtin-components.md | 19 | ||||
-rw-r--r-- | examples/minimal/package.json | 3 | ||||
-rw-r--r-- | packages/astro/components/Debug.astro | 306 | ||||
-rw-r--r-- | packages/astro/package.json | 1 |
7 files changed, 350 insertions, 0 deletions
diff --git a/.changeset/wet-schools-dream.md b/.changeset/wet-schools-dream.md new file mode 100644 index 000000000..adaa06e3c --- /dev/null +++ b/.changeset/wet-schools-dream.md @@ -0,0 +1,14 @@ +--- +'astro': patch +--- + +Add `<Debug>` component for JavaScript-free client-side debugging. + +```astro +--- +import Debug from 'astro/debug'; +const obj = { /* ... */ } +--- + +<Debug {obj} /> +``` diff --git a/docs/src/config.ts b/docs/src/config.ts index 40833bac6..675913a23 100644 --- a/docs/src/config.ts +++ b/docs/src/config.ts @@ -18,6 +18,7 @@ export const SIDEBAR = { { text: 'Guides', header: true }, { text: 'Styling & CSS', link: 'guides/styling' }, { text: 'Markdown', link: 'guides/markdown-content' }, + { text: 'Debugging', link: 'guides/debugging' }, { text: 'Data Fetching', link: 'guides/data-fetching' }, { text: 'Pagination', link: 'guides/pagination' }, { text: 'RSS', link: 'guides/rss' }, diff --git a/docs/src/pages/guides/debugging.md b/docs/src/pages/guides/debugging.md new file mode 100644 index 000000000..561af0267 --- /dev/null +++ b/docs/src/pages/guides/debugging.md @@ -0,0 +1,6 @@ +--- +layout: ~/layouts/MainLayout.astro +title: Debugging +--- + +Astro runs on the server and logs directly to your terminal, so it can be difficult to debug values from Astro. Astro's built-in `<Debug>` component can help you inspect values inside your files on the clientside. Read more about the [built-in Debug Component](/reference/builtin-components#debug-). diff --git a/docs/src/pages/reference/builtin-components.md b/docs/src/pages/reference/builtin-components.md index d148c8ba0..ce64819d9 100644 --- a/docs/src/pages/reference/builtin-components.md +++ b/docs/src/pages/reference/builtin-components.md @@ -30,3 +30,22 @@ import { Prism } from 'astro/components'; ``` This component provides syntax highlighting for code blocks. Since this never changes in the client it makes sense to use an Astro component (it's equally reasonable to use a framework component for this kind of thing; Astro is server-only by default for all frameworks!). + + +## `<Debug />` + +```astro +--- +import { Debug } from 'astro/debug'; +const serverObject = { + a: 0, + b: "string", + c: { + nested: "object" + } +} +--- +<Debug {serverObject} /> +``` + +This component provides a way to inspect values on the clientside, without any JavaScript. diff --git a/examples/minimal/package.json b/examples/minimal/package.json index 72440233c..a1ee2ad24 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -9,5 +9,8 @@ }, "devDependencies": { "astro": "^0.19.2" + }, + "snowpack": { + "workspaceRoot": "../.." } } diff --git a/packages/astro/components/Debug.astro b/packages/astro/components/Debug.astro new file mode 100644 index 000000000..b09f870a7 --- /dev/null +++ b/packages/astro/components/Debug.astro @@ -0,0 +1,306 @@ +--- +const key = Object.keys(Astro.props)[0]; +const value = Astro.props[key]; + +const getType = (node: unknown) => { + if (Array.isArray(node)) return 'array'; + if (node === null) return 'null'; + if (typeof node === 'object') { + if ((node as any).then) return 'promise'; + } + return typeof node; +}; + +const getSummary = (node: any, key: string, className: string) => { + const type = getType(node); + let value; + let open; + let close; + + if (type === 'function') { + return <> + {(key || !key && key === 0) && <><span class={`${className} key`}>{key}</span><span class={`${className} sep`}>:</span></>} + <span class={`${className} value value-function`}>{node.name}<span class={`${className} punc`}>()</span></span> + </> + } + + if (type === 'promise') { + return <> + {(key || !key && key === 0) && <><span class={`${className} key`}>{key}</span><span class={`${className} sep`}>:</span></>} + <span class={`${className} value value-promise`}>Promise</span> + </> + } + + if (type === 'array') { + value = node.length; + open = <><span class={`${className} none`}>Array</span>{'['}</>; + close = ']'; + } else if (type === 'object') { + const keys = Object.keys(node); + if (keys.length === 0) { + value = 'Empty'; + } else if (keys.length > 3) { + value = '…'; + } else { + value = keys.slice(0, 3).join(','); + } + open = '{'; + close = '}'; + }; + + return <> + {key && <><span class={`${className} key`}>{key}</span>: </>} + {open && <span class={`${className} punc`}>{open}</span>} + <span class={`${className} hide`}> + <span class={`${className} len`}>{value}</span> + {close && <span class={`${className} punc`}>{close}</span>} + </span> + </>; +}; + +const Details = ({ node, key, children, class: className }) => { + const type = getType(node); + const props = {}; + + if (type === 'array' || type === 'object') { + props['data-char'] = type === 'array' ? ']' : '}' + props.open = !key && type === 'object' ? '' : undefined; + } + + return ( + <details {...props} class={className}> + <Summary node={node} key={key} class={className} /> + {children} + </details> + ); +} + +const Summary = ({ node, key, class: className }) => { + return ( + <summary class={className}>{getSummary(node, key, className)}</summary> + ); +} + +const Empty = Symbol('Empty'); + +const KeyValue = ({ key, value, dim, class: className }) => { + let type = key === '__proto__' ? 'prototype' : getType(value); + if (type === 'null') { + value = 'null'; + } else if (type === 'undefined') { + value = 'undefined'; + } else if (value === Empty) { + type = 'empty'; + value = 'Empty'; + } else { + value = JSON.stringify(value); + } + + return ( + <div class={`${className} line`}> + {(key || !key && key === 0) && <><span class={`${className} key ${dim ? 'key-dim' : ''}`.trim()}>{key}</span><span class={`${className} sep`}>:</span></>} + <span class={`${className} value value-${type}`}> + {value} + </span> + </div> + ) +} + +const Node = ({ node, key, class: className, ...props }) => { + const type = getType(node); + className = className.replace(/debug-value/g, ''); + + if (type === 'array' || type === 'object') { + let children = []; + if (type === 'array' && node.length > 0 && Object.entries(node).length === 0) { + children = Array.from({ length: node.length }, (_, key) => <Node node={Empty} key={key} class={className} />); + } else { + children = Object.entries(node).map(([key, value]) => <Node node={value} key={key} class={className} />); + } + return ( + <Details node={node} key={key} children={children} class={className} /> + ); + } else if (type === 'function') { + return ( + <Details node={node} key={key} class={className} children={ + <> + <KeyValue key="name" value={node.name} dim={true} class={className} /> + <KeyValue key="__proto__" value="Function" dim={true} class={className} /> + </> + }/> + ); + } else if (type === 'promise') { + return ( + <Details node={node} key={key} class={className} children={ + <KeyValue key="__proto__" value="Promise" dim={true} /> + } /> + ); + } + + return <KeyValue key={key} value={node} class={className} />; +} +--- + +<div class="debug"> + <div class="debug-header"> + <h2 class="debug-title"><span class="debug-label">Debug</span> <span class="debug-name">"{key}"</span></h2> + </div> + + <main> + <Node node={value} class="debug-value" /> + </main> +</div> + +<style lang="scss"> +.debug-header { + background: #FF1639; + margin: -1rem -1.5rem 1rem; + padding: 0.25rem 0.75rem; +} + +.debug-title { + font-size: 1em; + color: #fff; + margin: 0.5em 0; +} + +.debug-label { + font-weight: bold; + text-transform: uppercase; + margin-right: 0.75em; +} + +.debug { + all: initial; + display: flex; + flex-direction: column; + padding: 1rem 1.5rem; + overflow-y: hidden; + overflow-x: auto; + border: 1px solid #FFCFD6; + + background: #FFF; + font-family: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace; + font-size: 0.8rem; + line-height: 1.44; + color: #6b7280; + white-space: pre; + +} + +details[open] > summary span.hide { + visibility: hidden; +} + +details[open] > summary span.none { + display: none; + } + +details > details { + padding-left: 1em; + } + +.line { + padding-left: 1.125em; + } + +details[open]::after { + content: attr(data-char); + } + +.sep { + margin-right: 0.25em; + } + + +details > summary { + cursor: pointer; + } + +details:hover > summary::before, +details:focus > summary::before { + transform: translate(0.25em, -0.25em); +} + +details > summary::before { + content: ''; + background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13 8L2.60769 14L2.6077 2L13 8Z' fill='%236B7280' /%3E%3C/svg%3E%0A"); + background-size: 1em; + + display: inline-flex; + font-size: 0.5em; + width: 1em; + height: 1em; + margin-right: 1.25em; + margin-left: -2em; + transform: translate(0, -0.25em); + line-height: 1em; + transition: transform 120ms ease-in; +} + +details[open] > summary::before { + transform: translate(0.25em, -0.25em) rotate(90deg); +} + +.debug :global(::marker) { + content: ''; + width: 0; + visibility: hidden; +} + +.key { + color: #882de7; +} + +.key-dim { + color: #B881F1; +} + +.len { + color: #B881F1; +} + +details:hover > summary, +details:focus > summary, +details:hover > summary .punc, +details:focus > summary .punc, +details:hover[open]::after, +details:focus[open]::after { + color: #000012; +} + +details:hover > summary .len, +details:focus > summary .len { + color: #882DE7; +} + +.punc { + color: #6b7280; +} + +.value-string { + color: #17c083; +} + +.value-function::before { + content: 'ƒ '; + color: #3894ff; +} +.value-function { + color: #5076f9; +} + +.value-number { + color: #ff5d01; +} + +.value-null, +.value-undefined { + color: #9ca3af; +} + +main > .line { + margin-left: -0.75em; + padding-left: 0; +} +</style> diff --git a/packages/astro/package.json b/packages/astro/package.json index e359f3081..1fca0a9fc 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -16,6 +16,7 @@ "./snowpack-plugin": "./snowpack-plugin.cjs", "./snowpack-plugin-jsx": "./snowpack-plugin-jsx.cjs", "./components": "./components/index.js", + "./debug": "./components/Debug.astro", "./components/*": "./components/*", "./runtime/svelte": "./dist/frontend/runtime/svelte.js", "./internal/*": "./dist/internal/*", |