mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
feat(audits): Add initial perf audits (#10015)
* feat(audits): Add initial perf audits * feat(audits): Setup dev astro-island * fix(audits): Don't take scroll into account when getting an element's position * nit: lint * Fix tests * chore: changeset * maybe: Move this.hydrator outside the perf check * Update packages/astro/e2e/dev-toolbar.test.js Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> * address feedback * address feedback --------- Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
This commit is contained in:
parent
f9aebe74a1
commit
6884b103c8
18 changed files with 309 additions and 41 deletions
5
.changeset/twenty-plums-sell.md
Normal file
5
.changeset/twenty-plums-sell.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": minor
|
||||
---
|
||||
|
||||
Adds initial support for performance audits to the dev toolbar
|
19
.github/workflows/test-hosts.yml
vendored
19
.github/workflows/test-hosts.yml
vendored
|
@ -2,7 +2,7 @@ name: Hosted tests
|
|||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
env:
|
||||
ASTRO_TELEMETRY_DISABLED: true
|
||||
|
@ -28,24 +28,21 @@ jobs:
|
|||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
|
||||
- name: Build Astro
|
||||
run: pnpm turbo build --filter astro --filter @astrojs/vercel
|
||||
run: pnpm turbo build --filter astro --filter @astrojs/vercel
|
||||
|
||||
- name: Build test project
|
||||
working-directory: ./packages/integrations/vercel/test/hosted/hosted-astro-project
|
||||
run:
|
||||
pnpm run build
|
||||
|
||||
run: pnpm run build
|
||||
|
||||
- name: Deploy to Vercel
|
||||
working-directory: ./packages/integrations/vercel/test/hosted/hosted-astro-project
|
||||
run:
|
||||
pnpm dlx vercel --prod --prebuilt
|
||||
run: pnpm dlx vercel --prod --prebuilt
|
||||
|
||||
- name: Test
|
||||
run:
|
||||
pnpm run test:e2e:hosts
|
||||
run: pnpm run test:e2e:hosts
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,6 +22,7 @@ package-lock.json
|
|||
*.env
|
||||
|
||||
packages/astro/src/**/*.prebuilt.ts
|
||||
packages/astro/src/**/*.prebuilt-dev.ts
|
||||
!packages/astro/vendor/vite/dist
|
||||
packages/integrations/**/.netlify/
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ const additionalAttributes: HTMLAttributes<'img'> = {};
|
|||
if (image.srcSet.values.length > 0) {
|
||||
additionalAttributes.srcset = image.srcSet.attribute;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
additionalAttributes['data-image-component'] = 'true';
|
||||
}
|
||||
---
|
||||
|
||||
<img src={image.src} {...additionalAttributes} {...image.attributes} />
|
||||
|
|
|
@ -61,6 +61,10 @@ if (props.sizes) {
|
|||
if (fallbackImage.srcSet.values.length > 0) {
|
||||
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
imgAdditionalAttributes['data-image-component'] = 'true';
|
||||
}
|
||||
---
|
||||
|
||||
<picture {...pictureAttributes}>
|
||||
|
|
46
packages/astro/e2e/dev-toolbar-audits.test.js
Normal file
46
packages/astro/e2e/dev-toolbar-audits.test.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { testFactory } from './test-utils.js';
|
||||
|
||||
const test = testFactory({
|
||||
root: './fixtures/dev-toolbar/',
|
||||
});
|
||||
|
||||
let devServer;
|
||||
|
||||
test.beforeAll(async ({ astro }) => {
|
||||
devServer = await astro.startDevServer();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
test.describe('Dev Toolbar - Audits', () => {
|
||||
test('can warn about perf issues zzz', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/audits-perf'));
|
||||
|
||||
const toolbar = page.locator('astro-dev-toolbar');
|
||||
const appButton = toolbar.locator('button[data-app-id="astro:audit"]');
|
||||
await appButton.click();
|
||||
|
||||
const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]');
|
||||
const auditHighlights = auditCanvas.locator('astro-dev-toolbar-highlight');
|
||||
|
||||
const count = await auditHighlights.count();
|
||||
expect(count).toEqual(2);
|
||||
|
||||
for (const auditHighlight of await auditHighlights.all()) {
|
||||
await expect(auditHighlight).toBeVisible();
|
||||
|
||||
const auditCode = await auditHighlight.getAttribute('data-audit-code');
|
||||
expect(auditCode.startsWith('perf-')).toBe(true);
|
||||
|
||||
await auditHighlight.hover();
|
||||
const auditHighlightTooltip = auditHighlight.locator('astro-dev-toolbar-tooltip');
|
||||
await expect(auditHighlightTooltip).toBeVisible();
|
||||
}
|
||||
|
||||
// Toggle app off
|
||||
await appButton.click();
|
||||
});
|
||||
});
|
|
@ -98,17 +98,18 @@ test.describe('Dev Toolbar', () => {
|
|||
await appButton.click();
|
||||
|
||||
const auditCanvas = toolbar.locator('astro-dev-toolbar-app-canvas[data-app-id="astro:audit"]');
|
||||
const auditHighlight = auditCanvas.locator('astro-dev-toolbar-highlight');
|
||||
await expect(auditHighlight).toBeVisible();
|
||||
const auditHighlights = auditCanvas.locator('astro-dev-toolbar-highlight');
|
||||
|
||||
await auditHighlight.hover();
|
||||
const auditHighlightTooltip = auditHighlight.locator('astro-dev-toolbar-tooltip');
|
||||
await expect(auditHighlightTooltip).toBeVisible();
|
||||
for (const auditHighlight of await auditHighlights.all()) {
|
||||
await expect(auditHighlight).toBeVisible();
|
||||
|
||||
await auditHighlight.hover();
|
||||
const auditHighlightTooltip = auditHighlight.locator('astro-dev-toolbar-tooltip');
|
||||
await expect(auditHighlightTooltip).toBeVisible();
|
||||
}
|
||||
|
||||
// Toggle app off
|
||||
await appButton.click();
|
||||
await expect(auditHighlight).not.toBeVisible();
|
||||
await expect(auditHighlightTooltip).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('audit shows no issues message when there are no issues', async ({ page, astro }) => {
|
||||
|
@ -233,4 +234,17 @@ test.describe('Dev Toolbar', () => {
|
|||
await appButton.click();
|
||||
await expect(myAppWindow).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('islands include their server and client render time', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/'));
|
||||
|
||||
const island = page.locator('astro-island');
|
||||
await expect(island).toHaveCount(1);
|
||||
|
||||
const serverRenderTime = await island.getAttribute('server-render-time');
|
||||
const clientRenderTime = await island.getAttribute('client-render-time');
|
||||
|
||||
expect(serverRenderTime).not.toBe(null);
|
||||
expect(clientRenderTime).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
|
BIN
packages/astro/e2e/fixtures/dev-toolbar/src/light_walrus.avif
Normal file
BIN
packages/astro/e2e/fixtures/dev-toolbar/src/light_walrus.avif
Normal file
Binary file not shown.
|
@ -2,4 +2,4 @@
|
|||
|
||||
---
|
||||
|
||||
<img src="https://astro.build/assets/press/astro-logo-dark.svg" alt="Astro logo" />
|
||||
<div>Hey, there's no errors here!</div>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import walrus from "../light_walrus.avif";
|
||||
---
|
||||
|
||||
<Image src={walrus} loading="lazy" alt="A walrus" />
|
||||
|
||||
<div style="height: 9000px;"></div>
|
||||
|
||||
<Image src={walrus} loading="eager" alt="A walrus" />
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from '../utils/highlight.js';
|
||||
import { createWindowElement } from '../utils/window.js';
|
||||
import { a11y } from './a11y.js';
|
||||
import { perf } from './perf.js';
|
||||
|
||||
const icon =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1 20 16"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
|
||||
|
@ -28,10 +29,20 @@ export interface ResolvedAuditRule {
|
|||
|
||||
export interface AuditRuleWithSelector extends AuditRule {
|
||||
selector: string;
|
||||
match?: (element: Element) => boolean | null | undefined | void;
|
||||
match?: (
|
||||
element: Element
|
||||
) =>
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| void
|
||||
| Promise<boolean>
|
||||
| Promise<void>
|
||||
| Promise<null>
|
||||
| Promise<undefined>;
|
||||
}
|
||||
|
||||
const rules = [...a11y];
|
||||
const rules = [...a11y, ...perf];
|
||||
|
||||
const dynamicAuditRuleKeys: Array<keyof AuditRule> = ['title', 'message'];
|
||||
function resolveAuditRule(rule: AuditRule, element: Element): ResolvedAuditRule {
|
||||
|
@ -93,12 +104,16 @@ export default {
|
|||
matches = Array.from(elements);
|
||||
} else {
|
||||
for (const element of elements) {
|
||||
if (rule.match(element)) {
|
||||
if (await rule.match(element)) {
|
||||
matches.push(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const element of matches) {
|
||||
// Don't audit elements that already have an audit on them
|
||||
// TODO: This is a naive implementation, it'd be good to show all the audits for an element at the same time.
|
||||
if (audits.some((audit) => audit.auditedElement === element)) continue;
|
||||
|
||||
await createAuditProblem(rule, element);
|
||||
}
|
||||
}
|
||||
|
@ -146,10 +161,10 @@ export default {
|
|||
}
|
||||
</style>
|
||||
<header>
|
||||
<h1><astro-dev-toolbar-icon icon="check-circle"></astro-dev-toolbar-icon>No accessibility issues detected.</h1>
|
||||
<h1><astro-dev-toolbar-icon icon="check-circle"></astro-dev-toolbar-icon>No accessibility or performance issues detected.</h1>
|
||||
</header>
|
||||
<p>
|
||||
Nice work! This app scans the page and highlights common accessibility issues for you, like a missing "alt" attribute on an image.
|
||||
Nice work! This app scans the page and highlights common accessibility and performance issues for you, like a missing "alt" attribute on an image, or a image not using performant attributes.
|
||||
</p>
|
||||
`
|
||||
);
|
||||
|
@ -197,7 +212,7 @@ export default {
|
|||
}
|
||||
|
||||
const rect = originalElement.getBoundingClientRect();
|
||||
const highlight = createHighlight(rect, 'warning');
|
||||
const highlight = createHighlight(rect, 'warning', { 'data-audit-code': rule.code });
|
||||
const tooltip = buildAuditTooltip(rule, originalElement);
|
||||
|
||||
// Set the highlight/tooltip as being fixed position the highlighted element
|
||||
|
|
125
packages/astro/src/runtime/client/dev-toolbar/apps/audit/perf.ts
Normal file
125
packages/astro/src/runtime/client/dev-toolbar/apps/audit/perf.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import type { AuditRuleWithSelector } from './index.js';
|
||||
|
||||
// A regular expression to match external URLs
|
||||
const EXTERNAL_URL_REGEX = /^(?:[a-z+]+:)?\/\//i;
|
||||
|
||||
export const perf: AuditRuleWithSelector[] = [
|
||||
{
|
||||
code: 'perf-use-image-component',
|
||||
title: 'Use the Image component',
|
||||
message: 'This image could be replaced with the Image component to improve performance.',
|
||||
selector: 'img:not([data-image-component])',
|
||||
async match(element) {
|
||||
const src = element.getAttribute('src');
|
||||
if (!src) return false;
|
||||
|
||||
// Don't match data URIs, they're typically used for specific use-cases that the image component doesn't help with
|
||||
if (src.startsWith('data:')) return false;
|
||||
|
||||
// Ignore images that are smaller than 20KB, most of the time the image component won't really help with these, or they're used for specific use-cases (pixel tracking, etc.)
|
||||
// Ignore this test for remote images for now, fetching them can be very slow and possibly dangerous
|
||||
if (!EXTERNAL_URL_REGEX.test(src)) {
|
||||
const imageData = await fetch(src).then((response) => response.blob());
|
||||
if (imageData.size < 20480) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'perf-use-loading-lazy',
|
||||
title: 'Use the loading="lazy" attribute',
|
||||
message: (element) =>
|
||||
`This ${element.nodeName} tag is below the fold and could be lazy-loaded to improve performance.`,
|
||||
selector:
|
||||
'img:not([loading]), img[loading="eager"], iframe:not([loading]), iframe[loading="eager"]',
|
||||
match(element) {
|
||||
const htmlElement = element as HTMLImageElement | HTMLIFrameElement;
|
||||
// Ignore elements that are above the fold, they should be loaded eagerly
|
||||
if (htmlElement.offsetTop < window.innerHeight) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'perf-use-loading-eager',
|
||||
title: 'Use the loading="eager" attribute',
|
||||
message: (element) =>
|
||||
`This ${element.nodeName} tag is above the fold and could be eagerly-loaded to improve performance.`,
|
||||
selector: 'img[loading="lazy"], iframe[loading="lazy"]',
|
||||
match(element) {
|
||||
const htmlElement = element as HTMLImageElement | HTMLIFrameElement;
|
||||
|
||||
// Ignore elements that are below the fold, they should be loaded lazily
|
||||
if (htmlElement.offsetTop > window.innerHeight) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'perf-use-videos',
|
||||
title: 'Use videos instead of GIFs for large animations',
|
||||
message:
|
||||
'This GIF could be replaced with a video to reduce its file size and improve performance.',
|
||||
selector: 'img[src$=".gif"]',
|
||||
async match(element) {
|
||||
const src = element.getAttribute('src');
|
||||
if (!src) return false;
|
||||
|
||||
// Ignore remote URLs
|
||||
if (EXTERNAL_URL_REGEX.test(src)) return false;
|
||||
|
||||
// Ignore GIFs that are smaller than 100KB, those are typically small enough to not be a problem
|
||||
if (!EXTERNAL_URL_REGEX.test(src)) {
|
||||
const imageData = await fetch(src).then((response) => response.blob());
|
||||
if (imageData.size < 102400) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'perf-slow-component-server-render',
|
||||
title: 'Server-rendered component took a long time to render',
|
||||
message: (element) =>
|
||||
`This component took an unusually long time to render on the server (${getCleanRenderingTime(
|
||||
element.getAttribute('server-render-time')
|
||||
)}). This might be a sign that it's doing too much work on the server, or something is blocking rendering.`,
|
||||
selector: 'astro-island[server-render-time]',
|
||||
match(element) {
|
||||
const serverRenderTime = element.getAttribute('server-render-time');
|
||||
if (!serverRenderTime) return false;
|
||||
|
||||
const renderingTime = parseFloat(serverRenderTime);
|
||||
if (Number.isNaN(renderingTime)) return false;
|
||||
|
||||
return renderingTime > 500;
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'perf-slow-component-client-hydration',
|
||||
title: 'Client-rendered component took a long time to hydrate',
|
||||
message: (element) =>
|
||||
`This component took an unusually long time to render on the server (${getCleanRenderingTime(
|
||||
element.getAttribute('client-render-time')
|
||||
)}). This could be a sign that something is blocking the main thread and preventing the component from hydrating quickly.`,
|
||||
selector: 'astro-island[client-render-time]',
|
||||
match(element) {
|
||||
const clientRenderTime = element.getAttribute('client-render-time');
|
||||
if (!clientRenderTime) return false;
|
||||
|
||||
const renderingTime = parseFloat(clientRenderTime);
|
||||
if (Number.isNaN(renderingTime)) return false;
|
||||
|
||||
return renderingTime > 500;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function getCleanRenderingTime(time: string | null) {
|
||||
if (!time) return 'unknown';
|
||||
const renderingTime = parseFloat(time);
|
||||
if (Number.isNaN(renderingTime)) return 'unknown';
|
||||
|
||||
return renderingTime.toFixed(2) + 's';
|
||||
}
|
|
@ -1,10 +1,20 @@
|
|||
import type { DevToolbarHighlight } from '../../ui-library/highlight.js';
|
||||
import type { Icon } from '../../ui-library/icons.js';
|
||||
|
||||
export function createHighlight(rect: DOMRect, icon?: Icon) {
|
||||
export function createHighlight(
|
||||
rect: DOMRect,
|
||||
icon?: Icon,
|
||||
additionalAttributes?: Record<string, string>
|
||||
) {
|
||||
const highlight = document.createElement('astro-dev-toolbar-highlight');
|
||||
if (icon) highlight.icon = icon;
|
||||
|
||||
if (additionalAttributes) {
|
||||
for (const [key, value] of Object.entries(additionalAttributes)) {
|
||||
highlight.setAttribute(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
highlight.tabIndex = 0;
|
||||
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
|
|
|
@ -185,9 +185,17 @@ declare const Astro: {
|
|||
);
|
||||
throw e;
|
||||
}
|
||||
await this.hydrator(this)(this.Component, props, slots, {
|
||||
let hydrationTimeStart;
|
||||
const hydrator = this.hydrator(this);
|
||||
if (process.env.NODE_ENV === 'development') hydrationTimeStart = performance.now();
|
||||
await hydrator(this.Component, props, slots, {
|
||||
client: this.getAttribute('client'),
|
||||
});
|
||||
if (process.env.NODE_ENV === 'development' && hydrationTimeStart)
|
||||
this.setAttribute(
|
||||
'client-render-time',
|
||||
(performance.now() - hydrationTimeStart).toString()
|
||||
);
|
||||
this.removeAttribute('ssr');
|
||||
this.dispatchEvent(new CustomEvent('astro:hydrate'));
|
||||
};
|
||||
|
|
|
@ -184,6 +184,7 @@ async function renderFrameworkComponent(
|
|||
}
|
||||
}
|
||||
|
||||
let componentServerRenderEndTime;
|
||||
// If no one claimed the renderer
|
||||
if (!renderer) {
|
||||
if (metadata.hydrate === 'only') {
|
||||
|
@ -241,6 +242,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
if (metadata.hydrate === 'only') {
|
||||
html = await renderSlotToString(result, slots?.fallback);
|
||||
} else {
|
||||
const componentRenderStartTime = performance.now();
|
||||
({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call(
|
||||
{ result },
|
||||
Component,
|
||||
|
@ -248,6 +250,8 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
children,
|
||||
metadata
|
||||
));
|
||||
if (process.env.NODE_ENV === 'development')
|
||||
componentServerRenderEndTime = performance.now() - componentRenderStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,6 +331,9 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
metadata as Required<AstroComponentMetadata>
|
||||
);
|
||||
|
||||
if (componentServerRenderEndTime && process.env.NODE_ENV === 'development')
|
||||
island.props['server-render-time'] = componentServerRenderEndTime;
|
||||
|
||||
// Render template if not all astro fragments are provided.
|
||||
let unrenderedSlots: string[] = [];
|
||||
if (html) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { SSRResult } from '../../@types/astro.js';
|
||||
import islandScript from './astro-island.prebuilt.js';
|
||||
import islandScriptDev from './astro-island.prebuilt-dev.js';
|
||||
|
||||
const ISLAND_STYLES = `<style>astro-island,astro-slot,astro-static-slot{display:contents}</style>`;
|
||||
|
||||
|
@ -36,10 +37,9 @@ export function getPrescripts(result: SSRResult, type: PrescriptType, directive:
|
|||
// deps to be loaded immediately.
|
||||
switch (type) {
|
||||
case 'both':
|
||||
return `${ISLAND_STYLES}<script>${getDirectiveScriptText(
|
||||
result,
|
||||
directive
|
||||
)};${islandScript}</script>`;
|
||||
return `${ISLAND_STYLES}<script>${getDirectiveScriptText(result, directive)};${
|
||||
process.env.NODE_ENV === 'development' ? islandScriptDev : islandScript
|
||||
}</script>`;
|
||||
case 'directive':
|
||||
return `<script>${getDirectiveScriptText(result, directive)}</script>`;
|
||||
case null:
|
||||
|
|
|
@ -29,12 +29,12 @@ export default async function prebuild(...args) {
|
|||
))
|
||||
);
|
||||
|
||||
function getPrebuildURL(entryfilepath) {
|
||||
function getPrebuildURL(entryfilepath, dev = false) {
|
||||
const entryURL = pathToFileURL(entryfilepath);
|
||||
const basename = path.basename(entryfilepath);
|
||||
const ext = path.extname(entryfilepath);
|
||||
const name = basename.slice(0, basename.indexOf(ext));
|
||||
const outname = `${name}.prebuilt${ext}`;
|
||||
const outname = dev ? `${name}.prebuilt-dev${ext}` : `${name}.prebuilt${ext}`;
|
||||
const outURL = new URL('./' + outname, entryURL);
|
||||
return outURL;
|
||||
}
|
||||
|
@ -61,7 +61,8 @@ export default async function prebuild(...args) {
|
|||
}
|
||||
tscode = newTscode;
|
||||
}
|
||||
const esbuildresult = await esbuild.build({
|
||||
|
||||
const esbuildOptions = {
|
||||
stdin: {
|
||||
contents: tscode,
|
||||
resolveDir: path.dirname(filepath),
|
||||
|
@ -73,19 +74,40 @@ export default async function prebuild(...args) {
|
|||
minify,
|
||||
bundle: true,
|
||||
write: false,
|
||||
});
|
||||
const code = esbuildresult.outputFiles[0].text.trim();
|
||||
const rootURL = new URL('../../', import.meta.url);
|
||||
const rel = path.relative(fileURLToPath(rootURL), filepath);
|
||||
const mod = `/**
|
||||
};
|
||||
|
||||
const results = await Promise.all(
|
||||
[
|
||||
{
|
||||
build: await esbuild.build(esbuildOptions),
|
||||
dev: false,
|
||||
},
|
||||
filepath.includes('astro-island')
|
||||
? {
|
||||
build: await esbuild.build({
|
||||
...esbuildOptions,
|
||||
define: { 'process.env.NODE_ENV': '"development"' },
|
||||
}),
|
||||
dev: true,
|
||||
}
|
||||
: undefined,
|
||||
].filter((entry) => entry)
|
||||
);
|
||||
|
||||
for (const result of results) {
|
||||
const code = result.build.outputFiles[0].text.trim();
|
||||
const rootURL = new URL('../../', import.meta.url);
|
||||
const rel = path.relative(fileURLToPath(rootURL), filepath);
|
||||
const mod = `/**
|
||||
* This file is prebuilt from ${rel}
|
||||
* Do not edit this directly, but instead edit that file and rerun the prebuild
|
||||
* to generate this file.
|
||||
*/
|
||||
|
||||
export default \`${escapeTemplateLiterals(code)}\`;`;
|
||||
const url = getPrebuildURL(filepath);
|
||||
await fs.promises.writeFile(url, mod, 'utf-8');
|
||||
const url = getPrebuildURL(filepath, result.dev);
|
||||
await fs.promises.writeFile(url, mod, 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(entryPoints.map(prebuildFile));
|
||||
|
|
Loading…
Reference in a new issue