Compare commits

...

4 commits

Author SHA1 Message Date
Princesseuh
85d1d60cb5
fix: build 2023-10-05 21:09:26 +02:00
Princesseuh
4ba8b5c73f
fix: lockfile 2023-10-05 20:56:03 +02:00
Erika
5bb1398b2a
Merge branch 'main' into feat/dev-overlay 2023-10-05 20:53:14 +02:00
Princesseuh
66f7b5aa3f
feat: initial commit for dev overlay 2023-10-05 20:51:30 +02:00
26 changed files with 795 additions and 1 deletions

View file

@ -1,5 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { builtinModules } = require('module'); const { builtinModules } = require('module');
/** @type {import("@types/eslint").Linter.Config} */
module.exports = { module.exports = {
extends: [ extends: [
'plugin:@typescript-eslint/recommended-type-checked', 'plugin:@typescript-eslint/recommended-type-checked',
@ -69,6 +71,12 @@ module.exports = {
], ],
}, },
}, },
{
files: ['packages/astro/src/runtime/client/**/*.ts'],
env: {
browser: true,
},
},
{ {
files: ['packages/**/test/*.js', 'packages/**/*.js'], files: ['packages/**/test/*.js', 'packages/**/*.js'],
env: { env: {

View file

@ -0,0 +1 @@
FROM node:18-bullseye

21
examples/dev-overlay/.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View file

@ -0,0 +1,47 @@
# Astro Starter Kit: Dev Overlay Showcase
```sh
npm create astro@latest -- --template dev-overlay
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

View file

@ -0,0 +1,5 @@
export default {
id: 'custom',
title: "I'm a plugin!",
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="#fff" d="M7.563 19.28a9.693 9.693 0 0 0 2.496-.217a8.798 8.798 0 0 1 2.98-.131a7.78 7.78 0 0 1 1.289.257c1.077.275 2.61.223 3.005-.41c.29-.468.253-.787-.026-1.199c-.06-.09-.126-.17-.188-.235l-.024-.025a25.109 25.109 0 0 1-.743-.618a25.578 25.578 0 0 1-1.753-1.66a16.153 16.153 0 0 1-1.577-1.893l-.036-.053c-.742-1.139-1.558-1.067-2.002-.318a9.593 9.593 0 0 1-.955 1.332c-.41.482-.83.89-1.306 1.297c-.122.105-.502.42-.411.344c-.004.003-.017.015.05-.071c-.098.12-.95.877-1.2 1.162c-.515.583-.722 1.08-.645 1.48c.073.376.22.587.45.745a1.433 1.433 0 0 0 .48.206c.033.003.072.005.116.007Zm7.099-7.276c1.375 1.97 3.731 3.793 3.731 3.793s2.064 1.748.638 4.038c-1.426 2.29-5.253 1.278-5.253 1.278s-1.52-.49-3.286-.098c-1.765.395-3.286.245-3.286.245S5 21.015 4.553 18.701c-.446-2.314 2.06-4.04 2.258-4.284c.196-.247 1.512-1.073 2.452-2.658c.94-1.586 3.584-2.54 5.399.245Zm5.538-1.42c0 .457.191 2.393-1.552 2.432c-1.743.038-1.816-1.178-1.816-2.05c0-.913.187-2.205 1.59-2.205c1.4 0 1.778 1.369 1.778 1.824Zm-5.429-2.777c-1.18-.152-1.447-1.222-1.333-2.293c.095-.875 1.142-2.219 1.981-2.026c.837.19 1.6 1.3 1.446 2.254c-.152.957-.912 2.218-2.094 2.065ZM9.755 7.44c-.861 0-1.56-.993-1.56-2.22c0-1.227.698-2.22 1.56-2.22c.863 0 1.56.993 1.56 2.22c0 1.227-.697 2.22-1.56 2.22Zm-3.793 4.566c-1.695.365-2.327-1.597-2.14-2.515c0 0 .2-1.987 1.576-2.11c1.093-.095 1.898 1.101 1.98 1.785c.052.444.283 2.475-1.416 2.84Z"/></svg>',
};

View file

@ -0,0 +1,11 @@
import { defineConfig } from 'astro/config';
import solidJs from "@astrojs/solid-js";
// https://astro.build/config
export default defineConfig({
devTools: {
plugins: ['./astro-devtools-plugin.js']
},
integrations: [solidJs()]
});

View file

@ -0,0 +1,3 @@
export function HelloWorld(props: { text: string }) {
return <><div>{props.text}</div><br/></>;
}

View file

@ -0,0 +1,18 @@
{
"name": "@example/dev-overlay",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^3.2.3",
"@astrojs/solid-js": "^3.0.1",
"solid-js": "^1.7.11"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View file

@ -0,0 +1,23 @@
---
import { HelloWorld } from '../../components/Component';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body style="background-color: sandybrown;">
<h1>Astro</h1>
<h2>Audit</h2>
<img src="/astro-logo.png" />
<h2>Components</h2>
<HelloWorld text="No hydration" />
<HelloWorld text="client:load Hydrated" client:load name="hello" />
</body>
</html>

View file

@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
}

View file

@ -1084,6 +1084,10 @@ export interface AstroUserConfig {
remotePatterns?: Partial<RemotePattern>[]; remotePatterns?: Partial<RemotePattern>[];
}; };
devTools?: {
plugins: string[];
};
/** /**
* @docs * @docs
* @kind heading * @kind heading
@ -2263,3 +2267,10 @@ export interface ClientDirectiveConfig {
name: string; name: string;
entrypoint: string; entrypoint: string;
} }
export interface DevOverlayItem {
id: string;
name: string;
icon: string;
init?(canvas: ShadowRoot, eventTarget: EventTarget): void | Promise<void>;
}

View file

@ -32,6 +32,9 @@ const ASTRO_CONFIG_DEFAULTS = {
image: { image: {
service: { entrypoint: 'astro/assets/services/sharp', config: {} }, service: { entrypoint: 'astro/assets/services/sharp', config: {} },
}, },
devTools: {
plugins: [],
},
compressHTML: true, compressHTML: true,
server: { server: {
host: false, host: false,
@ -220,6 +223,9 @@ export const AstroConfigSchema = z.object({
.default([]), .default([]),
}) })
.default(ASTRO_CONFIG_DEFAULTS.image), .default(ASTRO_CONFIG_DEFAULTS.image),
devTools: z
.object({ plugins: z.array(z.string()).default([]) })
.default(ASTRO_CONFIG_DEFAULTS.devTools),
markdown: z markdown: z
.object({ .object({
drafts: z.boolean().default(false), drafts: z.boolean().default(false),

View file

@ -16,6 +16,7 @@ import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.j
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js'; import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js'; import astroVitePlugin from '../vite-plugin-astro/index.js';
import configAliasVitePlugin from '../vite-plugin-config-alias/index.js'; import configAliasVitePlugin from '../vite-plugin-config-alias/index.js';
import astroDevTools from '../vite-plugin-dev-tools/vite-plugin-dev-tools.js';
import envVitePlugin from '../vite-plugin-env/index.js'; import envVitePlugin from '../vite-plugin-env/index.js';
import astroHeadPlugin from '../vite-plugin-head/index.js'; import astroHeadPlugin from '../vite-plugin-head/index.js';
import htmlVitePlugin from '../vite-plugin-html/index.js'; import htmlVitePlugin from '../vite-plugin-html/index.js';
@ -134,6 +135,7 @@ export async function createVite(
vitePluginSSRManifest(), vitePluginSSRManifest(),
astroAssetsPlugin({ settings, logger, mode }), astroAssetsPlugin({ settings, logger, mode }),
astroTransitions(), astroTransitions(),
astroDevTools({ settings, logger }),
], ],
publicDir: fileURLToPath(settings.config.publicDir), publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(settings.config.root), root: fileURLToPath(settings.config.root),

View file

@ -0,0 +1,189 @@
// @ts-expect-error
import { loadDevToolsPlugins } from 'astro:dev-tools';
import type { DevOverlayItem as DevOverlayItemDefinition } from '../../../@types/astro.js';
import astroDevToolPlugin from './plugins/astro.js';
import astroAuditPlugin from './plugins/audit.js';
import astroXrayPlugin from './plugins/xray.js';
import { DevOverlayHighlight, DevOverlayTooltip, DevOverlayWindow } from './ui-toolkit.js';
type DevOverlayItem = DevOverlayItemDefinition & {
active: boolean;
inited: boolean;
eventTarget: EventTarget;
};
document.addEventListener('DOMContentLoaded', async () => {
const builtinPlugins: DevOverlayItem[] = [
astroDevToolPlugin,
astroXrayPlugin,
astroAuditPlugin,
].map((plugin) => ({ ...plugin, active: false, inited: false, eventTarget: new EventTarget() }));
const customPluginsImports = (await loadDevToolsPlugins()) as DevOverlayItem[];
const customPlugins: DevOverlayItem[] = [];
customPlugins.push(...customPluginsImports.map((plugin) => ({ ...plugin, active: false })));
const plugins: DevOverlayItem[] = [...builtinPlugins, ...customPlugins];
class AstroDevOverlay extends HTMLElement {
shadowRoot: ShadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: 'closed' });
}
// connect component
async connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
#dev-overlay {
display: inline-block;
position: fixed;
bottom: 7.5%;
left: 50%;
transform: translate(-50%, 0%);
height: 56px;
overflow: hidden;
background: linear-gradient(180deg, #13151A 0%, rgba(19, 21, 26, 0.88) 100%);
box-shadow: 0px 0px 0px 0px #13151A4D;
border: 1px solid #343841;
border-radius: 9999px;
z-index: 999999;
}
#dev-overlay .item {
display: flex;
justify-content: center;
align-items: center;
width: 64px;
}
#dev-overlay #bar-container .item:hover {
background: rgba(27, 30, 36, 1);
cursor: pointer;
}
#dev-overlay #bar-container .item.active {
background: rgba(71, 78, 94, 1);
}
#dev-overlay .item svg {
width: 24px;
height: 24px;
display: block;
margin: auto;
}
#dev-overlay #bar-container {
height: 100%;
display: flex;
}
#dev-overlay .separator {
background: rgba(52, 56, 65, 1);
width: 1px;
}
astro-overlay-plugin-canvas:not([data-active]) {
display: none;
}
</style>
<div id="dev-overlay">
<div id="bar-container">
${builtinPlugins.map((plugin) => this.getPluginTemplate(plugin)).join('')}
<div class="separator"></div>
${customPluginsImports.map((plugin) => this.getPluginTemplate(plugin)).join('')}
</div>
</div>`;
this.attachClickEvents();
}
attachClickEvents() {
const items = this.shadowRoot.querySelectorAll<HTMLDivElement>('.item');
if (!items) return;
items.forEach((item) => {
item.addEventListener('click', async (e) => {
const target = e.currentTarget;
if (!target || !(target instanceof HTMLElement)) return;
const id = target.dataset.pluginId;
if (!id) return;
const plugin = this.getPluginById(id);
if (!plugin) return;
const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;
if (!plugin.inited) {
await plugin.init?.(shadowRoot, plugin.eventTarget);
plugin.inited = true;
}
this.togglePluginStatus(plugin);
plugin.eventTarget.dispatchEvent(
new CustomEvent('plugin-toggle', {
detail: {
state: plugin.active,
plugin,
},
})
);
});
});
}
getPluginTemplate(plugin: DevOverlayItem) {
return `<div class="item" data-plugin-id="${plugin.id}">
<div class="icon">${plugin.icon}</div>
</div>`;
}
getPluginById(id: string) {
return plugins.find((plugin) => plugin.id === id);
}
getPluginCanvasById(id: string) {
return this.shadowRoot.querySelector(`astro-overlay-plugin-canvas[data-plugin-id="${id}"]`);
}
togglePluginStatus(plugin: DevOverlayItem, status?: boolean) {
plugin.active = status ?? !plugin.active;
const target = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`);
if (!target) return;
target.classList.toggle('active', plugin.active);
this.getPluginCanvasById(plugin.id)?.toggleAttribute('data-active', plugin.active);
}
}
class DevOverlayCanvas extends HTMLElement {
shadowRoot: ShadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: 'closed' });
}
// connect component
async connectedCallback() {
this.shadowRoot.innerHTML = ``;
}
}
customElements.define('astro-dev-overlay', AstroDevOverlay);
customElements.define('astro-dev-overlay-window', DevOverlayWindow);
customElements.define('astro-overlay-plugin-canvas', DevOverlayCanvas);
customElements.define('astro-overlay-tooltip', DevOverlayTooltip);
customElements.define('astro-overlay-highlight', DevOverlayHighlight);
const overlay = document.createElement('astro-dev-overlay');
overlay.style.zIndex = '999999';
document.body.appendChild(overlay);
// Create plugin canvases
plugins.forEach((plugin) => {
const pluginCanvas = document.createElement('astro-overlay-plugin-canvas');
pluginCanvas.dataset.pluginId = plugin.id;
overlay.shadowRoot?.appendChild(pluginCanvas);
});
});

View file

@ -0,0 +1,14 @@
import type { DevOverlayItem } from '../../../../@types/astro.js';
export default {
id: 'astro',
name: 'Astro',
icon: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 24"><path fill="#fff" d="M6.45385 20.9419c-1.08694-.9911-1.40425-3.0736-.9514-4.5823.78522.9512 1.8732 1.2525 3.00013 1.4226 1.73972.2625 3.44832.1643 5.06442-.6289.1849-.0908.3557-.2115.5578-.3338.1516.4388.1911.8819.1381 1.3328-.1288 1.0982-.6767 1.9465-1.5481 2.5896-.3485.2572-.7172.4871-1.0771.7297-1.1056.7454-1.4047 1.6194-.9893 2.8909.0099.0309.0187.0619.041.1375-.5645-.252-.9768-.6189-1.29099-1.1013-.33185-.5092-.48972-1.0725-.49803-1.682-.00416-.2966-.00416-.5958-.04414-.8882-.09764-.7129-.43312-1.032-1.06513-1.0504-.64864-.0189-1.16173.3811-1.29779 1.011-.01039.0483-.02545.0961-.04051.1523l.00104.0005Z"/><path fill="url(#a)" d="M6.45385 20.9419c-1.08694-.9911-1.40425-3.0736-.9514-4.5823.78522.9512 1.8732 1.2525 3.00013 1.4226 1.73972.2625 3.44832.1643 5.06442-.6289.1849-.0908.3557-.2115.5578-.3338.1516.4388.1911.8819.1381 1.3328-.1288 1.0982-.6767 1.9465-1.5481 2.5896-.3485.2572-.7172.4871-1.0771.7297-1.1056.7454-1.4047 1.6194-.9893 2.8909.0099.0309.0187.0619.041.1375-.5645-.252-.9768-.6189-1.29099-1.1013-.33185-.5092-.48972-1.0725-.49803-1.682-.00416-.2966-.00416-.5958-.04414-.8882-.09764-.7129-.43312-1.032-1.06513-1.0504-.64864-.0189-1.16173.3811-1.29779 1.011-.01039.0483-.02545.0961-.04051.1523l.00104.0005Z"/><path fill="#fff" d="M.25 16.1083s3.21861-1.5641 6.44622-1.5641l2.43351-7.51249c.0911-.36331.35712-.61021.65744-.61021.30033 0 .56633.2469.65743.61021l2.4335 7.51249c3.8226 0 6.4462 1.5641 6.4462 1.5641s-5.467-14.85637-5.4777-14.88618C13.6897.782887 13.4248.5 13.0676.5H6.50726c-.35713 0-.61133.282887-.77893.72212C5.71652 1.25137.25 16.1083.25 16.1083Z"/><defs><linearGradient id="a" x1="9.7873" x2="12.2634" y1="23.3025" y2="15.1217" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>',
init(canvas, eventTarget) {
eventTarget.addEventListener('plugin-toggle', (e) => {
if ((e as CustomEvent).detail.state === true) {
window.open('https://astro.build', '_blank');
}
});
},
} satisfies DevOverlayItem;

View file

@ -0,0 +1,121 @@
import type { DevOverlayItem } from '../../../../@types/astro.js';
import type { DevOverlayTooltip } from '../ui-toolkit.js';
interface AuditRule {
title: string;
message: string;
}
const selectorBasedRules: (AuditRule & { selector: string })[] = [
{
title: 'Missing `alt` tag',
message: 'The alt attribute is important for accessibility.',
selector: 'img:not([alt])',
},
];
export default {
id: 'astro:audit',
name: 'Audit',
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 16" fill="none"><path fill="#fff" d="M.625 2c0-.29837.118526-.58452.329505-.7955C1.16548.993526 1.45163.875 1.75.875h16.5c.2984 0 .5845.118526.7955.3295.211.21098.3295.49713.3295.7955 0 .29837-.1185.58452-.3295.7955-.211.21097-.4971.3295-.7955.3295H1.75c-.29837 0-.58452-.11853-.795495-.3295C.743526 2.58452.625 2.29837.625 2ZM1.75 9.125h6c.29837 0 .58452-.11853.7955-.32951.21097-.21097.3295-.49712.3295-.79549s-.11853-.58452-.3295-.7955c-.21098-.21097-.49713-.3295-.7955-.3295h-6c-.29837 0-.58452.11853-.795495.3295C.743526 7.41548.625 7.70163.625 8c0 .29837.118526.58452.329505.79549.210975.21098.497125.32951.795495.32951Zm7.5 3.75h-7.5c-.29837 0-.58452.1185-.795495.3295C.743526 13.4155.625 13.7016.625 14s.118526.5845.329505.7955c.210975.211.497125.3295.795495.3295h7.5c.29837 0 .58452-.1185.7955-.3295.211-.211.3295-.4971.3295-.7955s-.1185-.5845-.3295-.7955c-.21098-.211-.49713-.3295-.7955-.3295Zm11.2959 1.9209c-.1045.1049-.2287.1881-.3654.2449-.1368.0568-.2834.086-.4314.086-.1481 0-.2947-.0292-.4315-.086-.1367-.0568-.2609-.14-.3654-.2449l-1.695-1.695c-.8694.4849-1.8849.6389-2.859.4338s-1.8411-.7556-2.4412-1.5498c-.6001-.7943-.8927-1.7787-.8239-2.77183.0688-.99308.4944-1.92778 1.1983-2.63167.7039-.7039 1.6386-1.12951 2.6317-1.19832.9931-.06881 1.9775.22382 2.7718.82391.7942.60009 1.3447 1.46716 1.5498 2.44126.2051.9741.0511 1.98955-.4338 2.85895l1.695 1.6941c.1051.1045.1884.2287.2453.3656.0568.1368.0861.2835.0861.4317s-.0293.2949-.0861.4317c-.0569.1369-.1402.2611-.2453.3656ZM15.25 11.375c.3708 0 .7334-.11 1.0417-.316.3083-.206.5487-.4989.6906-.8415.1419-.34258.179-.71958.1067-1.0833-.0724-.36371-.251-.6978-.5132-.96003-.2622-.26222-.5963-.4408-.96-.51314-.3637-.07235-.7407-.03522-1.0833.1067-.3426.14191-.6355.38223-.8415.69058-.206.30834-.316.67085-.316 1.04169 0 .24623.0485.49005.1427.7175.0943.2275.2324.4342.4065.6083.1741.1741.3808.3122.6083.4065.2275.0942.4713.1427.7175.1427Z"/></svg>',
init(canvas) {
createBase();
selectorBasedRules.forEach((rule) => {
document.querySelectorAll(rule.selector).forEach((el) => {
canvas.appendChild(createAuditProblem(rule, el));
});
});
function createAuditProblem(rule: AuditRule, originalElement: Element) {
const el = document.createElement('div');
el.className = 'astro-audit-problem';
el.style.position = 'absolute';
const rect = originalElement.getBoundingClientRect();
el.style.top = `${Math.max(rect.top - 10, 0)}px`;
el.style.left = `${Math.max(rect.left - 10, 0)}px`;
el.style.width = `${rect.width + 15}px`;
el.style.height = `${rect.height + 15}px`;
el.innerHTML = `
<div class="icon" style="left: ${rect.width}px;">
<svg width="16px" height="16px">
<use xlink:href="#astro:audit:warning" width="16px" height="16px"></use>
</svg>
</div>
<astro-overlay-highlight></astro-overlay-highlight>
`;
const tooltip = document.createElement('astro-overlay-tooltip') as DevOverlayTooltip;
tooltip.sections = [
{
icon: '<svg width="16px" height="16px"><use xlink:href="#astro:audit:warning" width="16px" height="16px"></use></svg>',
title: rule.title,
},
{
content: rule.message,
},
{
content: '/src/somewhere/component.astro',
clickDescription: 'Click to go to file',
clickAction() {
// TODO: Implement this
},
},
];
tooltip.style.position = 'absolute';
tooltip.style.top = `${rect.height}px`;
tooltip.style.left = `${Math.max(rect.left - 10, 5)}px`;
tooltip.style.margin = '0';
el.appendChild(tooltip);
el.addEventListener('mouseover', () => {
tooltip.dialog.show();
});
el.addEventListener('mouseout', () => {
tooltip.dialog.close();
});
return el;
}
function createBase() {
// Create style
const style = document.createElement('style');
// TODO: Should this be in the astro-overlay-highlight component?
style.innerHTML = `
.astro-audit-problem .icon {
width: 24px;
height: 24px;
background: linear-gradient(0deg, #B33E66, #B33E66), linear-gradient(0deg, #351722, #351722);
border: 1px solid rgba(53, 23, 34, 1);
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: -15px;
}
`;
const svgSymbols = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgSymbols.setAttribute('aria-hidden', 'true');
svgSymbols.setAttribute(
'style',
'position: absolute; width: 0; height: 0; overflow: hidden;'
);
svgSymbols.innerHTML = `
<symbol viewBox="0 0 16 16" id="astro:audit:warning">
<path fill="#fff" d="M8 .40625c-1.5019 0-2.97007.445366-4.21886 1.27978C2.53236 2.52044 1.55905 3.70642.984293 5.094.40954 6.48157.259159 8.00842.552165 9.48147.845172 10.9545 1.56841 12.3076 2.63041 13.3696c1.06201 1.062 2.41508 1.7852 3.88813 2.0782 1.47304.293 2.99989.1427 4.38746-.4321 1.3876-.5747 2.5736-1.5481 3.408-2.7968.8344-1.2488 1.2798-2.717 1.2798-4.2189-.0023-2.0133-.8031-3.9435-2.2267-5.36713C11.9435 1.20925 10.0133.408483 8 .40625ZM8 13.9062c-1.16814 0-2.31006-.3463-3.28133-.9953-.97128-.649-1.7283-1.5715-2.17533-2.6507-.44703-1.0792-.56399-2.26675-.3361-3.41245.22789-1.1457.79041-2.1981 1.61641-3.0241.82601-.826 1.8784-1.38852 3.0241-1.61641 1.1457-.2279 2.33325-.11093 3.41245.3361 1.0793.44703 2.0017 1.20405 2.6507 2.17532.649.97128.9954 2.11319.9954 3.28134-.0017 1.56592-.6245 3.0672-1.7318 4.1745S9.56592 13.9046 8 13.9062Zm-.84375-5.62495V4.625c0-.22378.0889-.43839.24713-.59662.15824-.15824.37285-.24713.59662-.24713.22378 0 .43839.08889.59662.24713.15824.15823.24713.37284.24713.59662v3.65625c0 .22378-.08889.43839-.24713.59662C8.43839 9.03611 8.22378 9.125 8 9.125c-.22377 0-.43838-.08889-.59662-.24713-.15823-.15823-.24713-.37284-.24713-.59662ZM9.125 11.0938c0 .2225-.06598.44-.18959.625-.12362.185-.29932.3292-.50489.4143-.20556.0852-.43176.1074-.64999.064-.21823-.0434-.41869-.1505-.57602-.3079-.15734-.1573-.26448-.3577-.30789-.576-.04341-.2182-.02113-.4444.06402-.65.08515-.2055.22934-.3812.41435-.5049.185-.1236.40251-.18955.62501-.18955.29837 0 .58452.11855.7955.32955.21098.2109.3295.4971.3295.7955Z"/>
</symbol>
`;
canvas.appendChild(style);
canvas.appendChild(svgSymbols);
}
},
} satisfies DevOverlayItem;

View file

@ -0,0 +1,84 @@
import type { DevOverlayItem } from '../../../../@types/astro.js';
import type { DevOverlayTooltip } from '../ui-toolkit.js';
export default {
id: 'astro:xray',
name: 'Xray',
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" d="M7.875 1.5v-.375c0-.298369.11853-.584517.3295-.795495C8.41548.118526 8.70163 0 9 0c.29837 0 .58452.118526.7955.329505.211.210978.3295.497126.3295.795495V1.5c0 .29837-.1185.58452-.3295.7955-.21098.21097-.49713.3295-.7955.3295-.29837 0-.58452-.11853-.7955-.3295-.21097-.21098-.3295-.49713-.3295-.7955ZM1.5 10.125c.29837 0 .58452-.1185.7955-.3295.21097-.21098.3295-.49713.3295-.7955 0-.29837-.11853-.58452-.3295-.7955-.21098-.21097-.49713-.3295-.7955-.3295h-.375c-.298369 0-.584517.11853-.795495.3295C.118526 8.41548 0 8.70163 0 9c0 .29837.118526.58452.329505.7955.210978.211.497126.3295.795495.3295H1.5Zm10.5187-6.43313c.1402.04674.2882.0654.4356.05493.1474-.01046.2912-.04986.4234-.11594.1321-.06607.25-.15753.3468-.26916.0968-.11162.1707-.24122.2174-.38139l.375-1.125c.084-.28016.0555-.58202-.0793-.84158-.1348-.259563-.3654-.45642-.6429-.548838-.2775-.092417-.58-.07313-.8436.053773-.2635.126903-.4672.351445-.568.626025l-.375 1.125c-.0941.28283-.0721.59146.0611.85812.1332.26665.3669.46952.6495.56406ZM2.26875 11.3081l-1.125.375c-.144256.0433-.27836.115-.394363.2111-.116003.096-.211543.2144-.280956.348-.069412.1337-.111285.2799-.123135.43-.01185.1502.006564.3011.054149.444.047586.1429.123375.2748.222875.3878.099499.1131.220683.205.356368.2703.135682.0654.283112.1028.433532.1101.15042.0073.30078-.0156.44216-.0675l1.125-.375c.14425-.0433.27836-.115.39436-.2111.116-.096.21154-.2144.28095-.348.06942-.1337.11129-.2799.12314-.43.01185-.1502-.00656-.3011-.05415-.444-.04759-.1429-.12338-.2748-.22287-.3878-.0995-.1131-.22069-.205-.35637-.2703-.13569-.0654-.28311-.1028-.43353-.1101-.15042-.0073-.30078.0156-.44216.0675Zm18.55595 5.6766c.1742.1741.3124.3808.4066.6084.0943.2275.1428.4714.1428.7177 0 .2463-.0485.4902-.1428.7177-.0942.2275-.2324.4343-.4066.6084l-1.1888 1.1887c-.1741.1742-.3808.3124-.6084.4067-.2275.0942-.4714.1428-.7177.1428-.2462 0-.4901-.0486-.7177-.1428-.2275-.0943-.4342-.2325-.6084-.4067l-4.2665-4.2665-1.6041 3.6909c-.1436.3349-.3826.6201-.6872.8201-.3045.2001-.66121.3061-1.02559.3049h-.09375c-.3792-.0165-.74423-.1489-1.04594-.3792-.30172-.2303-.52562-.5475-.64156-.9089L2.71875 5.0775c-.10548-.32823-.11842-.6792-.0374-1.01431s.25287-.64139.49666-.88518c.24379-.24379.55007-.41564.88518-.49666.33511-.08102.68608-.06808 1.01431.0374L20.085 7.61906c.3591.11962.6736.34511.9021.64684.2286.30172.3604.66555.3783 1.04363.0178.37808-.0792.75267-.2782 1.07467-.1991.3219-.491.576-.8372.7289l-3.6909 1.605 4.2656 4.2666Zm-1.8563 1.3256-4.3903-4.3912c-.2158-.2161-.3755-.4817-.4653-.7736-.0898-.2919-.1069-.6013-.0499-.9013.057-.3001.1864-.5816.377-.8202.1906-.2387.4366-.4271.7167-.549l3.2812-1.42594L5.08969 5.08969 9.44812 18.4369l1.42598-3.2813c.122-.28.3105-.5259.5492-.7164.2388-.1905.5204-.3198.8205-.3767.1155-.0224.2329-.0337.3506-.0338.4969.0004.9733.198 1.3247.5494l4.3912 4.3913.6581-.6591Z"/></svg>',
init(canvas) {
const islands = document.querySelectorAll<HTMLElement>('astro-island');
islands.forEach((island) => {
const el = document.createElement('div');
el.style.position = 'absolute';
const rect = island.children[0]
? island.children[0].getBoundingClientRect()
: island.getBoundingClientRect();
el.style.top = `${Math.max(rect.top - 10, 0)}px`;
el.style.left = `${Math.max(rect.left - 10, 0)}px`;
el.style.width = `${rect.width + 15}px`;
el.style.height = `${rect.height + 15}px`;
el.innerHTML = `
<astro-overlay-highlight></astro-overlay-highlight>
`;
const islandProps = island.getAttribute('props')
? JSON.parse(island.getAttribute('props')!)
: {};
const islandClientDirective = island.getAttribute('client');
const tooltip = document.createElement('astro-overlay-tooltip') as DevOverlayTooltip;
tooltip.sections = [];
if (islandClientDirective) {
tooltip.sections.push({
title: 'Client directive',
content: `client:${islandClientDirective}`,
});
}
if (Object.keys(islandProps).length > 0) {
tooltip.sections.push({
title: 'Props',
content: `${Object.entries(islandProps)
.map((prop) => `<code>${prop[0]}=${getPropValue(prop[1] as any)}</code>`)
.join(', ')}`,
});
}
tooltip.sections.push({
content: '/src/somewhere/component.astro',
clickDescription: 'Click to go to file',
clickAction() {
// TODO: Implement this
},
});
tooltip.style.position = 'absolute';
tooltip.style.top = `${rect.height}px`;
tooltip.style.left = `${Math.max(rect.left - 10, 5)}px`;
tooltip.style.margin = '0';
el.appendChild(tooltip);
el.addEventListener('mouseover', () => {
tooltip.dialog.show();
});
el.addEventListener('mouseout', () => {
tooltip.dialog.close();
});
canvas.appendChild(el);
});
function getPropValue(prop: [number, any]) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, value] = prop;
return JSON.stringify(value, null, 2);
}
},
} satisfies DevOverlayItem;

View file

@ -0,0 +1,148 @@
export class DevOverlayWindow extends HTMLElement {
title: string;
constructor() {
super();
this.title = 'World';
}
async connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
#astro-dev-window {
background: linear-gradient(0deg, #13151A, #13151A), linear-gradient(0deg, #343841, #343841);
}
#astro-dev-window h1 {
color: #fff;
}
</style>
<div id="astro-dev-window">
<h1>${this.title}</h1>
</div>
`;
}
}
interface DevOverlayTooltipSection {
title?: string;
icon?: string;
content?: string;
clickAction?: () => void;
clickDescription?: string;
}
export class DevOverlayTooltip extends HTMLElement {
sections: DevOverlayTooltipSection[] = [];
dialog: HTMLDialogElement;
constructor() {
super();
this.dialog = document.createElement('dialog');
}
connectedCallback() {
this.style.width = '100%';
this.innerHTML = `
<style>
dialog {
color: white;
background: linear-gradient(0deg, #310A65, #310A65), linear-gradient(0deg, #7118E2, #7118E2);
border: 1px solid rgba(113, 24, 226, 1);
border-radius: 4px;
padding: 0;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 14px;
margin: 0;
}
dialog svg {
vertical-align: bottom;
margin-right: 4px;
}
dialog hr {
border: 1px solid rgba(136, 58, 234, 0.33);
padding: 0;
margin: 0;
}
dialog section {
padding: 8px;
}
.modal-title {
font-weight: bold;
display: inline-block;
}
.modal-title + div {
margin-top: 8px;
}
.modal-cta {
display: block;
font-weight: bold;
font-size: 0.9em;
}
.clickable-section {
background: rgba(113, 24, 226, 1);
}
code {
background: rgba(136, 58, 234, 0.33);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
border-radius: 2px;
font-size: 14px;
padding: 2px;
}
`;
this.sections.forEach((section, index) => {
const el = document.createElement('section');
if (section.clickAction) {
el.classList.add('clickable-section');
el.addEventListener('click', section.clickAction);
}
el.innerHTML = `
${section.icon ? `${section.icon}` : ''}
${section.title ? `<span class="modal-title">${section.title}</span>` : ''}
${section.content ? `<div>${section.content}</div>` : ''}
${section.clickDescription ? `<span class="modal-cta">${section.clickDescription}</span>` : ''}
`;
this.dialog.appendChild(el);
if (index < this.sections.length - 1) {
this.dialog.appendChild(document.createElement('hr'));
}
});
this.appendChild(this.dialog);
}
}
export class DevOverlayHighlight extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.innerHTML = `
<style>
astro-overlay-highlight {
background: linear-gradient(180deg, rgba(224, 204, 250, 0.33) 0%, rgba(224, 204, 250, 0.0825) 100%);
border: 1px solid rgba(113, 24, 226, 1);
border-radius: 4px;
display: block;
width: 100%;
height: 100%;
}
</style>
`;
}
}

View file

@ -12,7 +12,7 @@ import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
import { createRenderContext, getParamsAndProps, type SSROptions } from '../core/render/index.js'; import { createRenderContext, getParamsAndProps, type SSROptions } from '../core/render/index.js';
import { createRequest } from '../core/request.js'; import { createRequest } from '../core/request.js';
import { matchAllRoutes } from '../core/routing/index.js'; import { matchAllRoutes } from '../core/routing/index.js';
import { isPage } from '../core/util.js'; import { isPage, resolveIdToUrl } from '../core/util.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js';
import { isServerLikeOutput } from '../prerender/utils.js'; import { isServerLikeOutput } from '../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js'; import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
@ -275,6 +275,13 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
props: { type: 'module', src: '/@vite/client' }, props: { type: 'module', src: '/@vite/client' },
children: '', children: '',
}); });
scripts.add({
props: {
type: 'module',
src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/dev-overlay/overlay.js'),
},
children: '',
});
} }
// TODO: We should allow adding generic HTML elements to the head, not just scripts // TODO: We should allow adding generic HTML elements to the head, not just scripts

View file

@ -0,0 +1,31 @@
import type * as vite from 'vite';
import type { AstroPluginOptions } from '../@types/astro.js';
const VIRTUAL_MODULE_ID = 'astro:dev-tools';
const resolvedVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
export default function astroDevTools({ settings }: AstroPluginOptions): vite.Plugin {
return {
name: 'astro:dev-tools',
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return resolvedVirtualModuleId;
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `
export const loadDevToolsPlugins = async () => {
return [${settings.config.devTools.plugins.map((p) => `(await import('${p}')).default`).join(',')}];
};
`;
}
},
configureServer(server) {
// Example: wait for a client to connect before sending a message
server.ws.on('connection', () => {
server.ws.send('astro-dev-tools', { msg: 'hello' });
});
},
};
}

View file

@ -149,6 +149,18 @@ importers:
specifier: ^3.2.3 specifier: ^3.2.3
version: link:../../packages/astro version: link:../../packages/astro
examples/dev-overlay:
dependencies:
'@astrojs/solid-js':
specifier: ^3.0.1
version: link:../../packages/integrations/solid
astro:
specifier: ^3.2.3
version: link:../../packages/astro
solid-js:
specifier: ^1.7.11
version: 1.7.11
examples/framework-alpine: examples/framework-alpine:
dependencies: dependencies:
'@astrojs/alpinejs': '@astrojs/alpinejs':

View file

@ -2,5 +2,6 @@
"compilerOptions": { "compilerOptions": {
"allowJs": true "allowJs": true
}, },
"include": [".eslintrc.cjs"],
"extends": "./tsconfig.base.json" "extends": "./tsconfig.base.json"
} }