0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Add Astro <Debug/> component (#675)

* Initial MVP Debug component

* Document the prettifying of the input

* Just make `<Debug/>` a wrapper around `<Prism/>` lol

* feat: add details/summary debug component

* chore: remove Props (unused)

* fix: prefer `div` to semantic elements

* chore: format

* fix: prop-drill `class` into components

* fix: ensure `astro/components` are evaluated lazily

* feat(debug): export debug component from `astro/debug`

* fix: minimal example local snowpack config

* docs: add debugging docs

* chore: add changeset

* docs: update debug docs

Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
Caleb Jasik 2021-08-23 15:43:22 -05:00 committed by GitHub
parent 2fd004dcd9
commit efb41f22c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 350 additions and 0 deletions

View file

@ -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} />
```

View file

@ -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' },

View file

@ -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-).

View file

@ -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.

View file

@ -9,5 +9,8 @@
},
"devDependencies": {
"astro": "^0.19.2"
},
"snowpack": {
"workspaceRoot": "../.."
}
}

View file

@ -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>

View file

@ -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/*",