summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/wet-schools-dream.md14
-rw-r--r--docs/src/config.ts1
-rw-r--r--docs/src/pages/guides/debugging.md6
-rw-r--r--docs/src/pages/reference/builtin-components.md19
-rw-r--r--examples/minimal/package.json3
-rw-r--r--packages/astro/components/Debug.astro306
-rw-r--r--packages/astro/package.json1
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/*",