Build/bundle assets and CSS (#1786)
* Bundling CSS * Current progress of building assets * New build progress * Its finally working * Force css to go through the build * Prettier filenames * Split into separate CSS and HTML plugins * Always have at least one input * Bring back in sitemaps + output * Bring back srcset support * Bundle CSS * Bring back minify * Update dynamic tests * Update remaining tests * Linting * Fix remaining broken test * Use fs directly * Adding a changeset * Use path.posix * Debugging windows * More debugging * Pass URLs into readFile * Remove some debugging stuff * Remove force flag from transformWithVite * Update packages/astro/src/vite-plugin-build-css/index.ts Co-authored-by: Drew Powers <1369770+drwpow@users.noreply.github.com> Co-authored-by: Drew Powers <1369770+drwpow@users.noreply.github.com>
This commit is contained in:
parent
437203b74f
commit
fd52bceea4
29 changed files with 1211 additions and 403 deletions
5
.changeset/silly-peas-battle.md
Normal file
5
.changeset/silly-peas-battle.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Update the build to build/bundle assets
|
|
@ -66,7 +66,7 @@
|
|||
"@babel/traverse": "^7.15.4",
|
||||
"@proload/core": "^0.2.1",
|
||||
"@proload/plugin-tsm": "^0.1.0",
|
||||
"@web/rollup-plugin-html": "^1.10.1",
|
||||
"@web/parse5-utils": "^1.3.0",
|
||||
"astring": "^1.7.5",
|
||||
"ci-info": "^3.2.0",
|
||||
"connect": "^3.7.0",
|
||||
|
@ -80,6 +80,7 @@
|
|||
"mime": "^2.5.2",
|
||||
"morphdom": "^2.6.1",
|
||||
"node-fetch": "^2.6.5",
|
||||
"parse5": "^6.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"prismjs": "^1.25.0",
|
||||
"remark-slug": "^7.0.0",
|
||||
|
@ -91,6 +92,7 @@
|
|||
"shorthash": "^0.0.2",
|
||||
"slash": "^4.0.0",
|
||||
"sourcemap-codec": "^1.4.8",
|
||||
"srcset-parse": "^1.1.0",
|
||||
"string-width": "^5.0.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-indent": "^4.0.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type babel from '@babel/core';
|
||||
import type { z } from 'zod';
|
||||
import type { AstroConfigSchema } from '../core/config';
|
||||
import type { AstroComponentFactory } from '../runtime/server';
|
||||
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
||||
import type vite from '../../vendor/vite';
|
||||
|
||||
export interface AstroComponentMetadata {
|
||||
|
@ -139,11 +139,9 @@ export interface CollectionRSS {
|
|||
|
||||
/** Generic interface for a component (Astro, Svelte, React, etc.) */
|
||||
export interface ComponentInstance {
|
||||
$$metadata: {
|
||||
modules: { module: Record<string, unknown>; specifier: string }[];
|
||||
fileURL: URL;
|
||||
};
|
||||
$$metadata: Metadata;
|
||||
default: AstroComponentFactory;
|
||||
css?: string[];
|
||||
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ export interface TopLevelAstro {
|
|||
|
||||
export interface SSRMetadata {
|
||||
renderers: Renderer[];
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
export interface SSRResult {
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import type { InputHTMLOptions } from '@web/rollup-plugin-html';
|
||||
import type { AstroConfig, ComponentInstance, GetStaticPathsResult, ManifestData, RouteCache, RouteData, RSSResult } from '../../@types/astro-core';
|
||||
import type { LogOptions } from '../logger';
|
||||
import type { AllPagesData } from './types';
|
||||
import type { RenderedChunk } from 'rollup';
|
||||
|
||||
import { rollupPluginHTML } from '@web/rollup-plugin-html';
|
||||
import { rollupPluginAstroBuildHTML } from '../../vite-plugin-build-html/index.js';
|
||||
import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
|
||||
import fs from 'fs';
|
||||
import { bold, cyan, green, dim } from 'kleur/colors';
|
||||
import { bold, cyan, green } from 'kleur/colors';
|
||||
import { performance } from 'perf_hooks';
|
||||
import vite, { ViteDevServer } from '../vite.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createVite } from '../create-vite.js';
|
||||
import { pad } from '../dev/util.js';
|
||||
import { debug, defaultLogOptions, levels, timerMessage, warn } from '../logger.js';
|
||||
import { ssr } from '../ssr/index.js';
|
||||
import { preload as ssrPreload } from '../ssr/index.js';
|
||||
import { generatePaginateFunction } from '../ssr/paginate.js';
|
||||
import { createRouteManifest, validateGetStaticPathsModule, validateGetStaticPathsResult } from '../ssr/routing.js';
|
||||
import { generateRssFunction } from '../ssr/rss.js';
|
||||
|
@ -71,9 +73,9 @@ class AstroBuilder {
|
|||
this.viteServer = viteServer;
|
||||
debug(logging, 'build', timerMessage('Vite started', timer.viteStart));
|
||||
|
||||
timer.renderStart = performance.now();
|
||||
timer.loadStart = performance.now();
|
||||
const assets: Record<string, string> = {};
|
||||
const allPages: Record<string, RouteData & { paths: string[] }> = {};
|
||||
const allPages: AllPagesData = {};
|
||||
// Collect all routes ahead-of-time, before we start the build.
|
||||
// NOTE: This enforces that `getStaticPaths()` is only called once per route,
|
||||
// and is then cached across all future SSR builds. In the past, we've had trouble
|
||||
|
@ -82,7 +84,21 @@ class AstroBuilder {
|
|||
this.manifest.routes.map(async (route) => {
|
||||
// static route:
|
||||
if (route.pathname) {
|
||||
allPages[route.component] = { ...route, paths: [route.pathname] };
|
||||
allPages[route.component] = {
|
||||
route,
|
||||
paths: [route.pathname],
|
||||
preload: await ssrPreload({
|
||||
astroConfig: this.config,
|
||||
filePath: new URL(`./${route.component}`, this.config.projectRoot),
|
||||
logging,
|
||||
mode: 'production',
|
||||
origin,
|
||||
pathname: route.pathname,
|
||||
route,
|
||||
routeCache: this.routeCache,
|
||||
viteServer,
|
||||
})
|
||||
};
|
||||
return;
|
||||
}
|
||||
// dynamic route:
|
||||
|
@ -94,38 +110,37 @@ class AstroBuilder {
|
|||
}
|
||||
assets[fileURLToPath(rssFile)] = result.rss.xml;
|
||||
}
|
||||
allPages[route.component] = { ...route, paths: result.paths };
|
||||
allPages[route.component] = {
|
||||
route,
|
||||
paths: result.paths,
|
||||
preload: await ssrPreload({
|
||||
astroConfig: this.config,
|
||||
filePath: new URL(`./${route.component}`, this.config.projectRoot),
|
||||
logging,
|
||||
mode: 'production',
|
||||
origin,
|
||||
pathname: result.paths[0],
|
||||
route,
|
||||
routeCache: this.routeCache,
|
||||
viteServer,
|
||||
})
|
||||
};
|
||||
})
|
||||
);
|
||||
debug(logging, 'build', timerMessage('All pages loaded', timer.loadStart));
|
||||
|
||||
// After all routes have been collected, start building them.
|
||||
// TODO: test parallel vs. serial performance. Promise.all() may be
|
||||
// making debugging harder without any perf gain. If parallel is best,
|
||||
// then we should set a max number of parallel builds.
|
||||
const input: InputHTMLOptions[] = [];
|
||||
await Promise.all(
|
||||
Object.entries(allPages).map(([component, route]) =>
|
||||
Promise.all(
|
||||
route.paths.map(async (pathname) => {
|
||||
input.push({
|
||||
html: await ssr({
|
||||
astroConfig: this.config,
|
||||
filePath: new URL(`./${component}`, this.config.projectRoot),
|
||||
logging,
|
||||
mode: 'production',
|
||||
origin,
|
||||
pathname,
|
||||
route,
|
||||
routeCache: this.routeCache,
|
||||
viteServer,
|
||||
}),
|
||||
name: pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''),
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
debug(logging, 'build', timerMessage('All pages rendered', timer.renderStart));
|
||||
// Pure CSS chunks are chunks that only contain CSS.
|
||||
// This is all of them, and chunkToReferenceIdMap maps them to a hash id used to find the final file.
|
||||
const pureCSSChunks = new Set<RenderedChunk>();
|
||||
const chunkToReferenceIdMap = new Map<string, string>();
|
||||
|
||||
// This is a mapping of pathname to the string source of all collected
|
||||
// inline <style> for a page.
|
||||
const astroStyleMap = new Map<string, string>();
|
||||
// This is a virtual JS module that imports all dependent styles for a page.
|
||||
const astroPageStyleMap = new Map<string, string>();
|
||||
|
||||
const pageNames: string[] = [];
|
||||
|
||||
// Bundle the assets in your final build: This currently takes the HTML output
|
||||
// of every page (stored in memory) and bundles the assets pointed to on those pages.
|
||||
|
@ -138,17 +153,32 @@ class AstroBuilder {
|
|||
minify: 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles
|
||||
outDir: fileURLToPath(this.config.dist),
|
||||
rollupOptions: {
|
||||
// The `input` will be populated in the build rollup plugin.
|
||||
input: [],
|
||||
output: { format: 'esm' },
|
||||
},
|
||||
target: 'es2020', // must match an esbuild target
|
||||
},
|
||||
plugins: [
|
||||
rollupPluginHTML({
|
||||
rootDir: viteConfig.root,
|
||||
input,
|
||||
extractAssets: false,
|
||||
}) as any, // "any" needed for CI; also we don’t need typedefs for this anyway
|
||||
rollupPluginAstroBuildHTML({
|
||||
astroConfig: this.config,
|
||||
astroPageStyleMap,
|
||||
astroStyleMap,
|
||||
chunkToReferenceIdMap,
|
||||
pureCSSChunks,
|
||||
logging,
|
||||
origin,
|
||||
allPages,
|
||||
pageNames,
|
||||
routeCache: this.routeCache,
|
||||
viteServer
|
||||
}),
|
||||
rollupPluginAstroBuildCSS({
|
||||
astroPageStyleMap,
|
||||
astroStyleMap,
|
||||
chunkToReferenceIdMap,
|
||||
pureCSSChunks
|
||||
}),
|
||||
...(viteConfig.plugins || []),
|
||||
],
|
||||
publicDir: viteConfig.publicDir,
|
||||
|
@ -172,7 +202,7 @@ class AstroBuilder {
|
|||
timer.sitemapStart = performance.now();
|
||||
if (this.config.buildOptions.sitemap && this.config.buildOptions.site) {
|
||||
const sitemapStart = performance.now();
|
||||
const sitemap = generateSitemap(input.map(({ name }) => new URL(`/${name}`, this.config.buildOptions.site).href));
|
||||
const sitemap = generateSitemap(pageNames.map(pageName => new URL(`/${pageName}`, this.config.buildOptions.site).href));
|
||||
const sitemapPath = new URL('./sitemap.xml', this.config.dist);
|
||||
await fs.promises.mkdir(new URL('./', sitemapPath), { recursive: true });
|
||||
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
|
||||
|
@ -182,7 +212,7 @@ class AstroBuilder {
|
|||
// You're done! Time to clean up.
|
||||
await viteServer.close();
|
||||
if (logging.level && levels[logging.level] <= levels['info']) {
|
||||
await this.printStats({ cwd: this.config.dist, pageCount: input.length });
|
||||
await this.printStats({ cwd: this.config.dist, pageCount: pageNames.length });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
packages/astro/src/core/build/types.d.ts
vendored
Normal file
10
packages/astro/src/core/build/types.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
import type { ComponentPreload } from '../ssr/index';
|
||||
import type { RouteData } from '../../@types/astro-core';
|
||||
|
||||
export interface PageBuildData {
|
||||
paths: string[];
|
||||
preload: ComponentPreload;
|
||||
route: RouteData;
|
||||
}
|
||||
export type AllPagesData = Record<string, PageBuildData>;
|
||||
|
|
@ -7,7 +7,7 @@ import type { LogOptions } from '../logger';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { renderPage, renderSlot } from '../../runtime/server/index.js';
|
||||
import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency } from '../util.js';
|
||||
import { canonicalURL as getCanonicalURL, codeFrame, resolveDependency, viteifyPath } from '../util.js';
|
||||
import { getStylesForID } from './css.js';
|
||||
import { injectTags } from './html.js';
|
||||
import { generatePaginateFunction } from './paginate.js';
|
||||
|
@ -72,145 +72,174 @@ async function resolveRenderers(viteServer: vite.ViteDevServer, astroConfig: Ast
|
|||
return renderers;
|
||||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
export async function ssr({ astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer }: SSROptions): Promise<string> {
|
||||
try {
|
||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||
const renderers = await resolveRenderers(viteServer, astroConfig);
|
||||
// Load the module from the Vite SSR Runtime.
|
||||
const viteFriendlyURL = `/@fs${filePath.pathname}`;
|
||||
const mod = (await viteServer.ssrLoadModule(viteFriendlyURL)) as ComponentInstance;
|
||||
// Handle dynamic routes
|
||||
let params: Params = {};
|
||||
let pageProps: Props = {};
|
||||
if (route && !route.pathname) {
|
||||
if (route.params.length) {
|
||||
const paramsMatch = route.pattern.exec(pathname);
|
||||
if (paramsMatch) {
|
||||
params = getParams(route.params)(paramsMatch);
|
||||
}
|
||||
}
|
||||
validateGetStaticPathsModule(mod);
|
||||
if (!routeCache[route.component]) {
|
||||
routeCache[route.component] = await (
|
||||
await mod.getStaticPaths!({
|
||||
paginate: generatePaginateFunction(route),
|
||||
rss: () => {
|
||||
/* noop */
|
||||
},
|
||||
})
|
||||
).flat();
|
||||
}
|
||||
validateGetStaticPathsResult(routeCache[route.component], logging);
|
||||
const routePathParams: GetStaticPathsResult = routeCache[route.component];
|
||||
const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
|
||||
if (!matchedStaticPath) {
|
||||
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
|
||||
}
|
||||
pageProps = { ...matchedStaticPath.props } || {};
|
||||
}
|
||||
|
||||
// Validate the page component before rendering the page
|
||||
const Component = await mod.default;
|
||||
if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
||||
if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
|
||||
|
||||
// Create the result object that will be passed into the render function.
|
||||
// This object starts here as an empty shell (not yet the result) but then
|
||||
// calling the render() function will populate the object with scripts, styles, etc.
|
||||
const result: SSRResult = {
|
||||
styles: new Set<SSRElement>(),
|
||||
scripts: new Set<SSRElement>(),
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
|
||||
const site = new URL(origin);
|
||||
const url = new URL('.' + pathname, site);
|
||||
const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin);
|
||||
return {
|
||||
__proto__: astroGlobal,
|
||||
props,
|
||||
request: {
|
||||
canonicalURL,
|
||||
params,
|
||||
url,
|
||||
},
|
||||
slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
|
||||
// This is used for <Markdown> but shouldn't be used publicly
|
||||
privateRenderSlotDoNotUse(slotName: string) {
|
||||
return renderSlot(result, slots ? slots[slotName] : null);
|
||||
},
|
||||
// <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
|
||||
async privateRenderMarkdownDoNotUse(content: string, opts: any) {
|
||||
let render = astroConfig.markdownOptions.render;
|
||||
let renderOpts = {};
|
||||
if (Array.isArray(render)) {
|
||||
renderOpts = render[1];
|
||||
render = render[0];
|
||||
}
|
||||
if (typeof render === 'string') {
|
||||
({ default: render } = await import(render));
|
||||
}
|
||||
const { code } = await render(content, { ...renderOpts, ...(opts ?? {}) });
|
||||
return code;
|
||||
},
|
||||
} as unknown as AstroGlobal;
|
||||
},
|
||||
_metadata: { renderers },
|
||||
};
|
||||
|
||||
let html = await renderPage(result, Component, pageProps, null);
|
||||
|
||||
// inject tags
|
||||
const tags: vite.HtmlTagDescriptor[] = [];
|
||||
|
||||
// dev only: inject Astro HMR client
|
||||
if (mode === 'development') {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
attrs: { type: 'module' },
|
||||
children: `import 'astro/runtime/client/hmr.js';`,
|
||||
injectTo: 'head',
|
||||
});
|
||||
}
|
||||
|
||||
// inject CSS
|
||||
[...getStylesForID(filePath.pathname, viteServer)].forEach((href) => {
|
||||
tags.push({
|
||||
tag: 'link',
|
||||
attrs: { type: 'text/css', rel: 'stylesheet', href },
|
||||
injectTo: 'head',
|
||||
});
|
||||
});
|
||||
|
||||
// add injected tags
|
||||
html = injectTags(html, tags);
|
||||
|
||||
// run transformIndexHtml() in dev to run Vite dev transformations
|
||||
if (mode === 'development') {
|
||||
html = await viteServer.transformIndexHtml(filePath.pathname, html, pathname);
|
||||
}
|
||||
|
||||
return html;
|
||||
} catch (e: any) {
|
||||
async function errorHandler(e: unknown, viteServer: vite.ViteDevServer, filePath: URL) {
|
||||
if(e instanceof Error) {
|
||||
viteServer.ssrFixStacktrace(e);
|
||||
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
||||
if (e.errors) {
|
||||
const { location, pluginName, text } = (e as BuildResult).errors[0];
|
||||
const err = new Error(text) as SSRError;
|
||||
if (location) err.loc = { file: location.file, line: location.line, column: location.column };
|
||||
const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc);
|
||||
err.frame = frame;
|
||||
err.id = location?.file;
|
||||
err.message = `${location?.file}: ${text}
|
||||
}
|
||||
|
||||
// Astro error (thrown by esbuild so it needs to be formatted for Vite)
|
||||
const anyError = e as any;
|
||||
if (anyError.errors) {
|
||||
const { location, pluginName, text } = (e as BuildResult).errors[0];
|
||||
const err = new Error(text) as SSRError;
|
||||
if (location) err.loc = { file: location.file, line: location.line, column: location.column };
|
||||
const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc);
|
||||
err.frame = frame;
|
||||
err.id = location?.file;
|
||||
err.message = `${location?.file}: ${text}
|
||||
${frame}
|
||||
`;
|
||||
err.stack = e.stack;
|
||||
if (pluginName) err.plugin = pluginName;
|
||||
throw err;
|
||||
}
|
||||
err.stack = anyError.stack;
|
||||
if (pluginName) err.plugin = pluginName;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Generic error (probably from Vite, and already formatted)
|
||||
// Generic error (probably from Vite, and already formatted)
|
||||
throw e;
|
||||
}
|
||||
|
||||
export type ComponentPreload = [Renderer[], ComponentInstance];
|
||||
|
||||
export async function preload({ astroConfig, filePath, viteServer }: SSROptions): Promise<ComponentPreload> {
|
||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||
const renderers = await resolveRenderers(viteServer, astroConfig);
|
||||
// Load the module from the Vite SSR Runtime.
|
||||
const viteFriendlyURL = viteifyPath(filePath.pathname);
|
||||
const mod = (await viteServer.ssrLoadModule(viteFriendlyURL)) as ComponentInstance;
|
||||
|
||||
return [renderers, mod];
|
||||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<string> {
|
||||
const { astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer } = ssrOpts;
|
||||
|
||||
// Handle dynamic routes
|
||||
let params: Params = {};
|
||||
let pageProps: Props = {};
|
||||
if (route && !route.pathname) {
|
||||
if (route.params.length) {
|
||||
const paramsMatch = route.pattern.exec(pathname);
|
||||
if (paramsMatch) {
|
||||
params = getParams(route.params)(paramsMatch);
|
||||
}
|
||||
}
|
||||
validateGetStaticPathsModule(mod);
|
||||
if (!routeCache[route.component]) {
|
||||
routeCache[route.component] = await (
|
||||
await mod.getStaticPaths!({
|
||||
paginate: generatePaginateFunction(route),
|
||||
rss: () => {
|
||||
/* noop */
|
||||
},
|
||||
})
|
||||
).flat();
|
||||
}
|
||||
validateGetStaticPathsResult(routeCache[route.component], logging);
|
||||
const routePathParams: GetStaticPathsResult = routeCache[route.component];
|
||||
const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params));
|
||||
if (!matchedStaticPath) {
|
||||
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
|
||||
}
|
||||
pageProps = { ...matchedStaticPath.props } || {};
|
||||
}
|
||||
|
||||
// Validate the page component before rendering the page
|
||||
const Component = await mod.default;
|
||||
if (!Component) throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
||||
if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
|
||||
|
||||
// Create the result object that will be passed into the render function.
|
||||
// This object starts here as an empty shell (not yet the result) but then
|
||||
// calling the render() function will populate the object with scripts, styles, etc.
|
||||
const result: SSRResult = {
|
||||
styles: new Set<SSRElement>(),
|
||||
scripts: new Set<SSRElement>(),
|
||||
/** This function returns the `Astro` faux-global */
|
||||
createAstro(astroGlobal: TopLevelAstro, props: Record<string, any>, slots: Record<string, any> | null) {
|
||||
const site = new URL(origin);
|
||||
const url = new URL('.' + pathname, site);
|
||||
const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin);
|
||||
return {
|
||||
__proto__: astroGlobal,
|
||||
props,
|
||||
request: {
|
||||
canonicalURL,
|
||||
params,
|
||||
url,
|
||||
},
|
||||
slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])),
|
||||
// This is used for <Markdown> but shouldn't be used publicly
|
||||
privateRenderSlotDoNotUse(slotName: string) {
|
||||
return renderSlot(result, slots ? slots[slotName] : null);
|
||||
},
|
||||
// <Markdown> also needs the same `astroConfig.markdownOptions.render` as `.md` pages
|
||||
async privateRenderMarkdownDoNotUse(content: string, opts: any) {
|
||||
let mdRender = astroConfig.markdownOptions.render;
|
||||
let renderOpts = {};
|
||||
if (Array.isArray(mdRender)) {
|
||||
renderOpts = mdRender[1];
|
||||
mdRender = mdRender[0];
|
||||
}
|
||||
if (typeof mdRender === 'string') {
|
||||
({ default: mdRender } = await import(mdRender));
|
||||
}
|
||||
const { code } = await mdRender(content, { ...renderOpts, ...(opts ?? {}) });
|
||||
return code;
|
||||
},
|
||||
} as unknown as AstroGlobal;
|
||||
},
|
||||
_metadata: {
|
||||
renderers,
|
||||
pathname
|
||||
},
|
||||
};
|
||||
|
||||
let html = await renderPage(result, Component, pageProps, null);
|
||||
|
||||
// inject tags
|
||||
const tags: vite.HtmlTagDescriptor[] = [];
|
||||
|
||||
// dev only: inject Astro HMR client
|
||||
if (mode === 'development') {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
attrs: { type: 'module' },
|
||||
children: `import 'astro/runtime/client/hmr.js';`,
|
||||
injectTo: 'head',
|
||||
});
|
||||
}
|
||||
|
||||
// inject CSS
|
||||
[...getStylesForID(filePath.pathname, viteServer)].forEach((href) => {
|
||||
tags.push({
|
||||
tag: 'link',
|
||||
attrs: {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-astro-injected': true
|
||||
},
|
||||
injectTo: 'head',
|
||||
});
|
||||
});
|
||||
|
||||
// add injected tags
|
||||
html = injectTags(html, tags);
|
||||
|
||||
// run transformIndexHtml() in dev to run Vite dev transformations
|
||||
if (mode === 'development') {
|
||||
html = await viteServer.transformIndexHtml(filePath.pathname, html, pathname);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export async function ssr(ssrOpts: SSROptions): Promise<string> {
|
||||
try {
|
||||
const [renderers, mod] = await preload(ssrOpts);
|
||||
return render(renderers, mod, ssrOpts);
|
||||
} catch (e: unknown) {
|
||||
await errorHandler(e, ssrOpts.viteServer, ssrOpts.filePath);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,3 +71,7 @@ export function resolveDependency(dep: string, astroConfig: AstroConfig) {
|
|||
// For Windows compat, we need a fully resolved `file://` URL string
|
||||
return pathToFileURL(resolved).toString();
|
||||
}
|
||||
|
||||
export function viteifyPath(pathname: string): string {
|
||||
return `/@fs${pathname}`;
|
||||
}
|
|
@ -6,6 +6,7 @@ import shorthash from 'shorthash';
|
|||
import { extractDirectives, generateHydrateScript } from './hydration.js';
|
||||
import { serializeListValue } from './util.js';
|
||||
export { createMetadata } from './metadata.js';
|
||||
export type { Metadata } from './metadata';
|
||||
|
||||
// INVESTIGATE:
|
||||
// 2. Less anys when possible and make it well known when they are needed.
|
||||
|
@ -271,10 +272,16 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac
|
|||
const template = await renderToString(result, Component, props, children);
|
||||
const styles = Array.from(result.styles)
|
||||
.filter(uniqueElements)
|
||||
.map((style) => renderElement('style', style));
|
||||
.map((style) => renderElement('style', {
|
||||
...style,
|
||||
props: { ...style.props, 'astro-style': true }
|
||||
}));
|
||||
const scripts = Array.from(result.scripts)
|
||||
.filter(uniqueElements)
|
||||
.map((script) => renderElement('script', script));
|
||||
.map((script, i) => renderElement('script', {
|
||||
...script,
|
||||
props: { ...script.props, 'astro-script': result._metadata.pathname + '/script-' + i }
|
||||
}));
|
||||
return template.replace('</head>', styles.join('\n') + scripts.join('\n') + '</head>');
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ interface ComponentMetadata {
|
|||
componentUrl: string;
|
||||
}
|
||||
|
||||
class Metadata {
|
||||
export class Metadata {
|
||||
public fileURL: URL;
|
||||
private metadataCache: Map<any, ComponentMetadata | null>;
|
||||
constructor(fileURL: string, public modules: ModuleInfo[], components: any[]) {
|
||||
constructor(fileURL: string, public modules: ModuleInfo[], public hydratedComponents: any[], public hoisted: any[]) {
|
||||
this.fileURL = new URL(fileURL);
|
||||
this.metadataCache = new Map<any, ComponentMetadata | null>();
|
||||
}
|
||||
|
@ -26,6 +26,26 @@ class Metadata {
|
|||
return metadata?.componentExport || null;
|
||||
}
|
||||
|
||||
// Recursively collect all of the hydrated components' paths.
|
||||
getAllHydratedComponentPaths(): Set<string> {
|
||||
const paths = new Set<string>();
|
||||
for(const component of this.hydratedComponents) {
|
||||
const path = this.getPath(component);
|
||||
if(path) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
for(const {module: mod} of this.modules) {
|
||||
if(typeof mod.$$metadata !== 'undefined') {
|
||||
for(const path of mod.$$metadata.getAllHydratedComponentPaths()) {
|
||||
paths.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private getComponentMetadata(Component: any): ComponentMetadata | null {
|
||||
if (this.metadataCache.has(Component)) {
|
||||
return this.metadataCache.get(Component)!;
|
||||
|
@ -66,5 +86,5 @@ interface CreateMetadataOptions {
|
|||
}
|
||||
|
||||
export function createMetadata(fileURL: string, options: CreateMetadataOptions) {
|
||||
return new Metadata(fileURL, options.modules, options.hydratedComponents);
|
||||
return new Metadata(fileURL, options.modules, options.hydratedComponents, options.hoisted);
|
||||
}
|
||||
|
|
146
packages/astro/src/vite-plugin-build-css/index.ts
Normal file
146
packages/astro/src/vite-plugin-build-css/index.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
|
||||
import type { ResolveIdHook, LoadHook, RenderedChunk } from 'rollup';
|
||||
import type { Plugin as VitePlugin } from 'vite';
|
||||
|
||||
import { STYLE_EXTENSIONS } from '../core/ssr/css.js';
|
||||
import { getViteResolve, getViteLoad } from './resolve.js';
|
||||
import { getViteTransform, TransformHook } from '../vite-plugin-astro/styles.js';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
|
||||
|
||||
// This is a virtual module that represents the .astro <style> usage on a page
|
||||
const ASTRO_STYLE_PREFIX = '@astro-inline-style';
|
||||
|
||||
const ASTRO_PAGE_STYLE_PREFIX = '@astro-page-all-styles';
|
||||
|
||||
const isCSSRequest = (request: string) => STYLE_EXTENSIONS.has(path.extname(request));
|
||||
|
||||
export function getAstroPageStyleId(pathname: string) {
|
||||
let styleId = ASTRO_PAGE_STYLE_PREFIX + pathname;
|
||||
if(styleId.endsWith('/')) {
|
||||
styleId += 'index';
|
||||
}
|
||||
styleId += '.js';
|
||||
return styleId;
|
||||
}
|
||||
|
||||
export function getAstroStyleId(pathname: string) {
|
||||
let styleId = ASTRO_STYLE_PREFIX + pathname;
|
||||
if(styleId.endsWith('/')) {
|
||||
styleId += 'index';
|
||||
}
|
||||
styleId += '.css';
|
||||
return styleId;
|
||||
}
|
||||
|
||||
export function getAstroStylePathFromId(id: string) {
|
||||
return id.substr(ASTRO_STYLE_PREFIX.length + 1);
|
||||
}
|
||||
|
||||
function isStyleVirtualModule(id: string) {
|
||||
return id.startsWith(ASTRO_STYLE_PREFIX);
|
||||
}
|
||||
|
||||
function isPageStyleVirtualModule(id: string) {
|
||||
return id.startsWith(ASTRO_PAGE_STYLE_PREFIX);
|
||||
}
|
||||
|
||||
interface PluginOptions {
|
||||
astroStyleMap: Map<string, string>;
|
||||
astroPageStyleMap: Map<string, string>;
|
||||
chunkToReferenceIdMap: Map<string, string>;
|
||||
pureCSSChunks: Set<RenderedChunk>;
|
||||
}
|
||||
|
||||
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||
const { astroPageStyleMap, astroStyleMap, chunkToReferenceIdMap, pureCSSChunks } = options;
|
||||
const styleSourceMap = new Map<string, string>();
|
||||
|
||||
let viteTransform: TransformHook;
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
|
||||
enforce: 'pre',
|
||||
|
||||
configResolved(resolvedConfig) {
|
||||
viteTransform = getViteTransform(resolvedConfig);
|
||||
},
|
||||
|
||||
async resolveId(id) {
|
||||
if(isPageStyleVirtualModule(id)) {
|
||||
return id;
|
||||
}
|
||||
if(isStyleVirtualModule(id)) {
|
||||
return id;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
if(isPageStyleVirtualModule(id)) {
|
||||
const source = astroPageStyleMap.get(id)!;
|
||||
return source;
|
||||
}
|
||||
if(isStyleVirtualModule(id)) {
|
||||
return astroStyleMap.get(id)!;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async transform(value, id) {
|
||||
if(isStyleVirtualModule(id)) {
|
||||
styleSourceMap.set(id, value);
|
||||
return null;
|
||||
}
|
||||
if(isCSSRequest(id)) {
|
||||
let result = await viteTransform(value, id);
|
||||
if(result) {
|
||||
styleSourceMap.set(id, result.code);
|
||||
} else {
|
||||
styleSourceMap.set(id, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
renderChunk(_code, chunk) {
|
||||
let chunkCSS = '';
|
||||
let isPureCSS = true;
|
||||
for(const [id] of Object.entries(chunk.modules)) {
|
||||
if(!isCSSRequest(id) && !isPageStyleVirtualModule(id)) {
|
||||
isPureCSS = false;
|
||||
}
|
||||
if(styleSourceMap.has(id)) {
|
||||
chunkCSS += styleSourceMap.get(id)!;
|
||||
}
|
||||
}
|
||||
|
||||
if(isPureCSS) {
|
||||
const referenceId = this.emitFile({
|
||||
name: chunk.name + '.css',
|
||||
type: 'asset',
|
||||
source: chunkCSS
|
||||
});
|
||||
pureCSSChunks.add(chunk);
|
||||
chunkToReferenceIdMap.set(chunk.fileName, referenceId);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Delete CSS chunks so JS is not produced for them.
|
||||
generateBundle(_options, bundle) {
|
||||
for(const [chunkId, chunk] of Object.entries(bundle)) {
|
||||
if(chunk.type === 'chunk' && pureCSSChunks.has(chunk)) {
|
||||
delete bundle[chunkId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
packages/astro/src/vite-plugin-build-css/resolve.ts
Normal file
27
packages/astro/src/vite-plugin-build-css/resolve.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import type { ResolveIdHook, LoadHook } from 'rollup';
|
||||
import type { ResolvedConfig, Plugin as VitePlugin } from 'vite';
|
||||
|
||||
export function getVitePluginByName(viteConfig: ResolvedConfig, pluginName: string): VitePlugin {
|
||||
const plugin = viteConfig.plugins.find(({ name }) => name === pluginName);
|
||||
if (!plugin) throw new Error(`${pluginName} plugin couldn’t be found`);
|
||||
return plugin;}
|
||||
|
||||
export function getViteResolvePlugin(viteConfig: ResolvedConfig): VitePlugin {
|
||||
return getVitePluginByName(viteConfig, 'vite:resolve');
|
||||
}
|
||||
|
||||
export function getViteLoadFallbackPlugin(viteConfig: ResolvedConfig): VitePlugin {
|
||||
return getVitePluginByName(viteConfig, 'vite:load-fallback');
|
||||
}
|
||||
|
||||
export function getViteResolve(viteConfig: ResolvedConfig): ResolveIdHook {
|
||||
const plugin = getViteResolvePlugin(viteConfig);
|
||||
if (!plugin.resolveId) throw new Error(`vite:resolve has no resolveId() hook`);
|
||||
return plugin.resolveId.bind(null as any) as any;
|
||||
}
|
||||
|
||||
export function getViteLoad(viteConfig: ResolvedConfig): LoadHook {
|
||||
const plugin = getViteLoadFallbackPlugin(viteConfig);
|
||||
if (!plugin.load) throw new Error(`vite:load-fallback has no load() hook`);
|
||||
return plugin.load.bind(null as any) as any;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { InputOptions } from 'rollup';
|
||||
|
||||
function fromEntries<V>(entries: [string, V][]) {
|
||||
const obj: Record<string, V> = {};
|
||||
for (const [k, v] of entries) {
|
||||
obj[k] = v;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function addRollupInput(
|
||||
inputOptions: InputOptions,
|
||||
newInputs: string[]
|
||||
): InputOptions {
|
||||
// Add input module ids to existing input option, whether it's a string, array or object
|
||||
// this way you can use multiple html plugins all adding their own inputs
|
||||
if (!inputOptions.input) {
|
||||
return { ...inputOptions, input: newInputs };
|
||||
}
|
||||
|
||||
if (typeof inputOptions.input === 'string') {
|
||||
return {
|
||||
...inputOptions,
|
||||
input: [inputOptions.input, ...newInputs],
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(inputOptions.input)) {
|
||||
return {
|
||||
...inputOptions,
|
||||
input: [...inputOptions.input, ...newInputs],
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof inputOptions.input === 'object') {
|
||||
return {
|
||||
...inputOptions,
|
||||
input: {
|
||||
...inputOptions.input,
|
||||
...fromEntries(
|
||||
newInputs
|
||||
.map(i => [i.split('/').slice(-1)[0].split('.')[0], i]),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unknown rollup input type. Supported inputs are string, array and object.`);
|
||||
}
|
204
packages/astro/src/vite-plugin-build-html/extract-assets.ts
Normal file
204
packages/astro/src/vite-plugin-build-html/extract-assets.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
import { Document, Element, Node } from 'parse5';
|
||||
import npath from 'path';
|
||||
import { findElements, getTagName, getAttribute, findNodes } from '@web/parse5-utils';
|
||||
import adapter from 'parse5/lib/tree-adapters/default.js';
|
||||
|
||||
const hashedLinkRels = ['stylesheet', 'preload'];
|
||||
const linkRels = [...hashedLinkRels, 'icon', 'manifest', 'apple-touch-icon', 'mask-icon'];
|
||||
|
||||
function getSrcSetUrls(srcset: string) {
|
||||
if (!srcset) {
|
||||
return [];
|
||||
}
|
||||
const srcsetParts = srcset.includes(',') ? srcset.split(',') : [srcset];
|
||||
const urls = srcsetParts
|
||||
.map(url => url.trim())
|
||||
.map(url => (url.includes(' ') ? url.split(' ')[0] : url));
|
||||
return urls;
|
||||
}
|
||||
|
||||
function extractFirstUrlOfSrcSet(node: Element) {
|
||||
const srcset = getAttribute(node, 'srcset');
|
||||
if (!srcset) {
|
||||
return '';
|
||||
}
|
||||
const urls = getSrcSetUrls(srcset);
|
||||
return urls[0];
|
||||
}
|
||||
|
||||
function isAsset(node: Element) {
|
||||
let path = '';
|
||||
switch (getTagName(node)) {
|
||||
case 'img':
|
||||
path = getAttribute(node, 'src') ?? '';
|
||||
break;
|
||||
case 'source':
|
||||
path = extractFirstUrlOfSrcSet(node) ?? '';
|
||||
break;
|
||||
case 'link':
|
||||
if (linkRels.includes(getAttribute(node, 'rel') ?? '')) {
|
||||
path = getAttribute(node, 'href') ?? '';
|
||||
}
|
||||
break;
|
||||
case 'meta':
|
||||
if (getAttribute(node, 'property') === 'og:image' && getAttribute(node, 'content')) {
|
||||
path = getAttribute(node, 'content') ?? '';
|
||||
}
|
||||
break;
|
||||
case 'script':
|
||||
if (getAttribute(node, 'type') !== 'module' && getAttribute(node, 'src')) {
|
||||
path = getAttribute(node, 'src') ?? '';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
new URL(path);
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isInlineScript(node: Element): boolean {
|
||||
switch (getTagName(node)) {
|
||||
case 'script':
|
||||
if (getAttribute(node, 'type') === 'module' && !getAttribute(node, 'src')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInlineStyle(node: Element): boolean {
|
||||
return getTagName(node) === 'style';
|
||||
}
|
||||
|
||||
export function isStylesheetLink(node: Element): boolean {
|
||||
return getTagName(node) === 'link' && getAttribute(node, 'rel') === 'stylesheet';
|
||||
}
|
||||
|
||||
export function isHashedAsset(node: Element) {
|
||||
switch (getTagName(node)) {
|
||||
case 'img':
|
||||
return true;
|
||||
case 'source':
|
||||
return true;
|
||||
case 'script':
|
||||
return true;
|
||||
case 'link':
|
||||
return hashedLinkRels.includes(getAttribute(node, 'rel')!);
|
||||
case 'meta':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveAssetFilePath(
|
||||
browserPath: string,
|
||||
htmlDir: string,
|
||||
projectRootDir: string,
|
||||
absolutePathPrefix?: string,
|
||||
) {
|
||||
const _browserPath =
|
||||
absolutePathPrefix && browserPath[0] === '/'
|
||||
? '/' + npath.posix.relative(absolutePathPrefix, browserPath)
|
||||
: browserPath;
|
||||
return npath.join(
|
||||
_browserPath.startsWith('/') ? projectRootDir : htmlDir,
|
||||
_browserPath.split('/').join(npath.sep),
|
||||
);
|
||||
}
|
||||
|
||||
export function getSourceAttribute(node: Element) {
|
||||
switch (getTagName(node)) {
|
||||
case 'img': {
|
||||
return 'src';
|
||||
}
|
||||
case 'source': {
|
||||
return 'srcset';
|
||||
}
|
||||
case 'link': {
|
||||
return 'href';
|
||||
}
|
||||
case 'script': {
|
||||
return 'src';
|
||||
}
|
||||
case 'meta': {
|
||||
return 'content';
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown node with tagname ${getTagName(node)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
export function getSourcePaths(node: Element) {
|
||||
const key = getSourceAttribute(node);
|
||||
|
||||
let location: Location = { start: 0, end: 0 };
|
||||
const src = getAttribute(node, key);
|
||||
if(node.sourceCodeLocation) {
|
||||
let loc = node.sourceCodeLocation.attrs[key];
|
||||
if(loc) {
|
||||
location.start = loc.startOffset;
|
||||
location.end = loc.endOffset;
|
||||
}
|
||||
}
|
||||
if (typeof key !== 'string' || src === '') {
|
||||
throw new Error(`Missing attribute ${key} in element ${node.nodeName}`);
|
||||
}
|
||||
|
||||
let paths: {path: string, location: Location}[] = [];
|
||||
if(src && key === 'srcset') {
|
||||
paths = getSrcSetUrls(src).map(path => ({
|
||||
path,
|
||||
location
|
||||
}));
|
||||
} else if(src) {
|
||||
paths.push({
|
||||
path: src,
|
||||
location
|
||||
});
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export function getTextContent(node: Node): string {
|
||||
if (adapter.isCommentNode(node)) {
|
||||
return node.data || '';
|
||||
}
|
||||
if (adapter.isTextNode(node)) {
|
||||
return node.value || '';
|
||||
}
|
||||
const subtree = findNodes(node, n => adapter.isTextNode(n));
|
||||
return subtree.map(getTextContent).join('');
|
||||
}
|
||||
|
||||
export function findAssets(document: Document) {
|
||||
return findElements(document, isAsset);
|
||||
}
|
||||
|
||||
export function findInlineScripts(document: Document) {
|
||||
return findElements(document, isInlineScript);
|
||||
}
|
||||
|
||||
export function findInlineStyles(document: Document) {
|
||||
return findElements(document, isInlineStyle);
|
||||
}
|
||||
|
||||
export function findStyleLinks(document: Document) {
|
||||
return findElements(document, isStylesheetLink);
|
||||
}
|
391
packages/astro/src/vite-plugin-build-html/index.ts
Normal file
391
packages/astro/src/vite-plugin-build-html/index.ts
Normal file
|
@ -0,0 +1,391 @@
|
|||
|
||||
import type { AstroConfig, RouteCache } from '../@types/astro-core';
|
||||
import type { LogOptions } from '../core/logger';
|
||||
import type { ViteDevServer, Plugin as VitePlugin } from 'vite';
|
||||
import type { OutputChunk, PreRenderedChunk, RenderedChunk } from 'rollup';
|
||||
import type { AllPagesData } from '../core/build/types';
|
||||
import parse5 from 'parse5';
|
||||
import srcsetParse from 'srcset-parse';
|
||||
import * as npath from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { getAttribute, getTagName, insertBefore, remove, createScript, createElement, setAttribute } from '@web/parse5-utils';
|
||||
import { addRollupInput } from './add-rollup-input.js';
|
||||
import { findAssets, findInlineScripts, findInlineStyles, getTextContent, isStylesheetLink } from './extract-assets.js';
|
||||
import { render as ssrRender } from '../core/ssr/index.js';
|
||||
import { getAstroStyleId, getAstroPageStyleId } from '../vite-plugin-build-css/index.js';
|
||||
import { viteifyPath } from '../core/util.js';
|
||||
|
||||
// This package isn't real ESM, so have to coerce it
|
||||
const matchSrcset: typeof srcsetParse = (srcsetParse as any).default;
|
||||
|
||||
const PLUGIN_NAME = '@astro/rollup-plugin-build';
|
||||
const ASTRO_PAGE_PREFIX = '@astro-page';
|
||||
const ASTRO_SCRIPT_PREFIX = '@astro-script';
|
||||
|
||||
const ASTRO_EMPTY = '@astro-empty';
|
||||
|
||||
const tagsWithSrcSet = new Set(['img', 'source']);
|
||||
|
||||
const isAstroInjectedLink = (node: parse5.Element) => isStylesheetLink(node) && getAttribute(node, 'data-astro-injected') === '';
|
||||
const isBuildableLink = (node: parse5.Element, srcRoot: string) => isAstroInjectedLink(node) || getAttribute(node, 'href')?.startsWith(srcRoot);
|
||||
const isBuildableImage = (node: parse5.Element, srcRoot: string) => getTagName(node) === 'img' && getAttribute(node, 'src')?.startsWith(srcRoot);
|
||||
const hasSrcSet = (node: parse5.Element) => tagsWithSrcSet.has(getTagName(node)) && !!getAttribute(node, 'srcset');
|
||||
|
||||
interface PluginOptions {
|
||||
astroConfig: AstroConfig;
|
||||
astroStyleMap: Map<string, string>;
|
||||
astroPageStyleMap: Map<string, string>;
|
||||
chunkToReferenceIdMap: Map<string, string>;
|
||||
logging: LogOptions;
|
||||
allPages: AllPagesData;
|
||||
pageNames: string[];
|
||||
pureCSSChunks: Set<RenderedChunk>;
|
||||
origin: string;
|
||||
routeCache: RouteCache;
|
||||
viteServer: ViteDevServer;
|
||||
}
|
||||
|
||||
export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin {
|
||||
const { astroConfig, astroStyleMap, astroPageStyleMap, chunkToReferenceIdMap, pureCSSChunks, logging, origin, allPages, routeCache, viteServer, pageNames } = options;
|
||||
|
||||
const srcRoot = astroConfig.src.pathname;
|
||||
|
||||
// A map of pages to rendered HTML
|
||||
const renderedPageMap = new Map<string, string>();
|
||||
|
||||
//
|
||||
const astroScriptMap = new Map<string, string>();
|
||||
const astroPageMap = new Map<string, string>();
|
||||
const astroAssetMap = new Map<string, Promise<Buffer>>();
|
||||
|
||||
const cssChunkMap = new Map<string, string[]>();
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
|
||||
enforce: 'pre',
|
||||
|
||||
async options(inputOptions) {
|
||||
const htmlInput: Set<string> = new Set();
|
||||
const assetInput: Set<string> = new Set(); // TODO remove?
|
||||
const jsInput: Set<string> = new Set();
|
||||
|
||||
for(const [component, pageData] of Object.entries(allPages)) {
|
||||
const [renderers, mod] = pageData.preload;
|
||||
|
||||
for(const path of mod.$$metadata.getAllHydratedComponentPaths()) {
|
||||
jsInput.add(path);
|
||||
}
|
||||
|
||||
for(const pathname of pageData.paths) {
|
||||
pageNames.push(pathname.replace(/\/?$/, '/index.html').replace(/^\//, ''));
|
||||
const id = ASTRO_PAGE_PREFIX + pathname;
|
||||
const html = await ssrRender(renderers, mod, {
|
||||
astroConfig,
|
||||
filePath: new URL(`./${component}`, astroConfig.projectRoot),
|
||||
logging,
|
||||
mode: 'production',
|
||||
origin,
|
||||
pathname,
|
||||
route: pageData.route,
|
||||
routeCache,
|
||||
viteServer,
|
||||
});
|
||||
renderedPageMap.set(id, html);
|
||||
|
||||
const document = parse5.parse(html, {
|
||||
sourceCodeLocationInfo: true
|
||||
});
|
||||
|
||||
const frontEndImports = [];
|
||||
for(const script of findInlineScripts(document)) {
|
||||
const astroScript = getAttribute(script, 'astro-script');
|
||||
if(astroScript) {
|
||||
const js = getTextContent(script);
|
||||
const scriptId = ASTRO_SCRIPT_PREFIX + astroScript;
|
||||
frontEndImports.push(scriptId);
|
||||
astroScriptMap.set(scriptId, js);
|
||||
}
|
||||
}
|
||||
|
||||
let styles = '';
|
||||
for(const node of findInlineStyles(document)) {
|
||||
if(getAttribute(node, 'astro-style')) {
|
||||
styles += getTextContent(node);
|
||||
}
|
||||
}
|
||||
|
||||
const assetImports = [];
|
||||
for(let node of findAssets(document)) {
|
||||
if(isBuildableLink(node, srcRoot)) {
|
||||
const href = getAttribute(node, 'href')!;
|
||||
const linkId = viteifyPath(href);
|
||||
assetImports.push(linkId);
|
||||
}
|
||||
|
||||
if(isBuildableImage(node, srcRoot)) {
|
||||
const src = getAttribute(node, 'src');
|
||||
if(src?.startsWith(srcRoot) && !astroAssetMap.has(src)) {
|
||||
astroAssetMap.set(src, fs.readFile(new URL(`file://${src}`)));
|
||||
}
|
||||
}
|
||||
|
||||
if(hasSrcSet(node)) {
|
||||
const candidates = matchSrcset(getAttribute(node, 'srcset')!);
|
||||
for(const {url} of candidates) {
|
||||
if(url.startsWith(srcRoot) && !astroAssetMap.has(url)) {
|
||||
astroAssetMap.set(url, fs.readFile(new URL(`file://${url}`)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(styles) {
|
||||
const styleId = getAstroStyleId(pathname);
|
||||
astroStyleMap.set(styleId, styles);
|
||||
// Put this at the front of imports
|
||||
assetImports.unshift(styleId);
|
||||
}
|
||||
|
||||
if(frontEndImports.length) {
|
||||
htmlInput.add(id);
|
||||
const jsSource = frontEndImports.map(sid => `import '${sid}';`).join('\n');
|
||||
astroPageMap.set(id, jsSource);
|
||||
}
|
||||
|
||||
if(assetImports.length) {
|
||||
const pageStyleId = getAstroPageStyleId(pathname);
|
||||
const jsSource = assetImports.map(sid => `import '${sid}';`).join('\n');
|
||||
astroPageStyleMap.set(pageStyleId, jsSource);
|
||||
assetInput.add(pageStyleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allInputs = new Set([...jsInput, ...htmlInput, ...assetInput]);
|
||||
// You always need at least 1 input, so add an placeholder just so we can build HTML/CSS
|
||||
if(!allInputs.size) {
|
||||
allInputs.add(ASTRO_EMPTY);
|
||||
}
|
||||
const outOptions = addRollupInput(inputOptions, Array.from(allInputs));
|
||||
return outOptions;
|
||||
},
|
||||
|
||||
|
||||
async resolveId(id) {
|
||||
switch(true) {
|
||||
case astroScriptMap.has(id):
|
||||
case astroPageMap.has(id):
|
||||
case id === ASTRO_EMPTY: {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
// Load pages
|
||||
if(astroPageMap.has(id)) {
|
||||
return astroPageMap.get(id)!;
|
||||
}
|
||||
// Load scripts
|
||||
if(astroScriptMap.has(id)) {
|
||||
return astroScriptMap.get(id)!;
|
||||
}
|
||||
// Give this module actual code so it doesnt warn about an empty chunk
|
||||
if(id === ASTRO_EMPTY) {
|
||||
return 'console.log("empty");';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
outputOptions(outputOptions) {
|
||||
Object.assign(outputOptions, {
|
||||
entryFileNames(chunk: PreRenderedChunk) {
|
||||
// Removes the `@astro-page` prefix from JS chunk names.
|
||||
if(chunk.name.startsWith(ASTRO_PAGE_PREFIX)) {
|
||||
let pageName = chunk.name.substr(ASTRO_PAGE_PREFIX.length + 1);
|
||||
if(!pageName) {
|
||||
pageName = 'index';
|
||||
}
|
||||
return `assets/${pageName}.[hash].js`;
|
||||
}
|
||||
return 'assets/[name].[hash].js';
|
||||
}
|
||||
});
|
||||
return outputOptions;
|
||||
},
|
||||
|
||||
async generateBundle(_options, bundle) {
|
||||
const facadeIdMap = new Map<string, string>();
|
||||
for(const [chunkId, output] of Object.entries(bundle)) {
|
||||
if(output.type === 'chunk') {
|
||||
const chunk = output as OutputChunk;
|
||||
const id = chunk.facadeModuleId;
|
||||
if(id === ASTRO_EMPTY) {
|
||||
delete bundle[chunkId];
|
||||
} else if(id) {
|
||||
facadeIdMap.set(id, chunk.fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit assets (images, etc)
|
||||
const assetIdMap = new Map<string, string>();
|
||||
for(const [assetPath, dataPromise] of astroAssetMap) {
|
||||
const referenceId = this.emitFile({
|
||||
type: 'asset',
|
||||
name: npath.basename(assetPath),
|
||||
source: await dataPromise
|
||||
});
|
||||
assetIdMap.set(assetPath, referenceId);
|
||||
}
|
||||
|
||||
// Create a mapping of chunks to dependent chunks, used to add the proper
|
||||
// link tags for CSS.
|
||||
for(const chunk of pureCSSChunks) {
|
||||
const referenceId = chunkToReferenceIdMap.get(chunk.fileName)!;
|
||||
const chunkReferenceIds = [referenceId];
|
||||
for(const imp of chunk.imports) {
|
||||
if(chunkToReferenceIdMap.has(imp)) {
|
||||
chunkReferenceIds.push(chunkToReferenceIdMap.get(imp)!);
|
||||
}
|
||||
}
|
||||
for(const [id] of Object.entries(chunk.modules)) {
|
||||
cssChunkMap.set(id, chunkReferenceIds);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of links added so we don't do so twice.
|
||||
const linkChunksAdded = new Set<string>();
|
||||
const appendStyleChunksBefore = (ref: parse5.Element, pathname: string, referenceIds: string[] | undefined, attrs: Record<string, any> = {}) => {
|
||||
let added = false;
|
||||
if(referenceIds) {
|
||||
const lastNode = ref;
|
||||
for(const referenceId of referenceIds) {
|
||||
const chunkFileName = this.getFileName(referenceId);
|
||||
const relPath = npath.posix.relative(pathname, '/' + chunkFileName);
|
||||
|
||||
// This prevents added links more than once per type.
|
||||
const key = pathname + relPath + attrs.rel || 'stylesheet';
|
||||
if(!linkChunksAdded.has(key)) {
|
||||
linkChunksAdded.add(key);
|
||||
insertBefore(lastNode.parentNode, createElement('link', {
|
||||
rel: 'stylesheet',
|
||||
...attrs,
|
||||
href: relPath
|
||||
}), lastNode);
|
||||
added = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
for(const [id, html] of renderedPageMap) {
|
||||
const pathname = id.substr(ASTRO_PAGE_PREFIX.length);
|
||||
const document = parse5.parse(html, {
|
||||
sourceCodeLocationInfo: true
|
||||
});
|
||||
|
||||
if(facadeIdMap.has(id)) {
|
||||
const bundleId = facadeIdMap.get(id)!;
|
||||
const bundlePath = '/' + bundleId;
|
||||
|
||||
// Update scripts
|
||||
let i = 0;
|
||||
for(let script of findInlineScripts(document)) {
|
||||
if(getAttribute(script, 'astro-script')) {
|
||||
if(i === 0) {
|
||||
const relPath = npath.posix.relative(pathname, bundlePath);
|
||||
insertBefore(script.parentNode, createScript({
|
||||
type: 'module',
|
||||
src: relPath
|
||||
}), script);
|
||||
}
|
||||
remove(script);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const styleId = getAstroPageStyleId(pathname);
|
||||
let pageCSSAdded = false;
|
||||
for(const node of findAssets(document)) {
|
||||
if(isBuildableLink(node, srcRoot)) {
|
||||
const rel = getAttribute(node, 'rel');
|
||||
switch(rel) {
|
||||
case 'stylesheet': {
|
||||
if(!pageCSSAdded) {
|
||||
const attrs = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));
|
||||
delete attrs['data-astro-injected'];
|
||||
pageCSSAdded = appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
|
||||
}
|
||||
remove(node);
|
||||
break;
|
||||
}
|
||||
case 'preload': {
|
||||
if(getAttribute(node, 'as') === 'style') {
|
||||
const attrs = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));
|
||||
appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
|
||||
remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isBuildableImage(node, srcRoot)) {
|
||||
const src = getAttribute(node, 'src')!;
|
||||
const referenceId = assetIdMap.get(src);
|
||||
if(referenceId) {
|
||||
const fileName = this.getFileName(referenceId);
|
||||
const relPath = npath.posix.relative(pathname, '/' + fileName);
|
||||
setAttribute(node, 'src', relPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Could be a `source` or an `img`.
|
||||
if(hasSrcSet(node)) {
|
||||
const srcset = getAttribute(node, 'srcset')!;
|
||||
let changedSrcset = srcset;
|
||||
const urls = matchSrcset(srcset).map(c => c.url);
|
||||
for(const url of urls) {
|
||||
if(assetIdMap.has(url)) {
|
||||
const referenceId = assetIdMap.get(url)!;
|
||||
const fileName = this.getFileName(referenceId);
|
||||
const relPath = npath.posix.relative(pathname, '/' + fileName);
|
||||
changedSrcset = changedSrcset.replace(url, relPath);
|
||||
}
|
||||
}
|
||||
// If anything changed, update it
|
||||
if(changedSrcset !== srcset) {
|
||||
setAttribute(node, 'srcset', changedSrcset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page styles for <style> usage, if not already appended via links.
|
||||
for(const style of findInlineStyles(document)) {
|
||||
if(getAttribute(style, 'astro-style')) {
|
||||
if(!pageCSSAdded) {
|
||||
pageCSSAdded = appendStyleChunksBefore(style, pathname, cssChunkMap.get(styleId));
|
||||
}
|
||||
|
||||
remove(style);
|
||||
}
|
||||
}
|
||||
|
||||
const outHTML = parse5.serialize(document);
|
||||
const outPath = npath.posix.join(pathname.substr(1), 'index.html');
|
||||
this.emitFile({
|
||||
fileName: outPath,
|
||||
source: outHTML,
|
||||
type: 'asset'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +1,45 @@
|
|||
/**
|
||||
* UNCOMMENT: add support for automatic <img> and srcset in build
|
||||
import { expect } from 'chai';
|
||||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import srcsetParse from 'srcset-parse';
|
||||
|
||||
let fixture;
|
||||
// This package isn't real ESM, so have to coerce it
|
||||
const matchSrcset = (srcsetParse).default;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-assets/' });
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
// TODO: add automatic asset bundling
|
||||
// Asset bundling
|
||||
describe('Assets', () => {
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-assets/' });
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('built the base image', async () => {
|
||||
await fixture.readFile('/images/twitter.png');
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const imgPath = $('img').attr('src');
|
||||
const data = await fixture.readFile('/' + imgPath);
|
||||
expect(!!data).to.equal(true);
|
||||
});
|
||||
|
||||
it('built the 2x image', async () => {
|
||||
await fixture.readFile('/images/twitter@2x.png');
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const srcset = $('img').attr('srcset');
|
||||
const candidates = matchSrcset(srcset);
|
||||
const match = candidates.find(a => a.density === 2);
|
||||
const data = await fixture.readFile('/' + match.url);
|
||||
expect(!!data).to.equal(true);
|
||||
});
|
||||
|
||||
it('built the 3x image', async () => {
|
||||
await fixture.readFile('/images/twitter@3x.png');
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const srcset = $('img').attr('srcset');
|
||||
const candidates = matchSrcset(srcset);
|
||||
const match = candidates.find(a => a.density === 3);
|
||||
const data = await fixture.readFile('/' + match.url);
|
||||
expect(!!data).to.equal(true);
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it.skip('is skipped', () => {});
|
||||
});
|
|
@ -1,28 +1,26 @@
|
|||
/**
|
||||
* UNCOMMENT: implement CSS bundling
|
||||
import { expect } from 'chai';
|
||||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
// note: the hashes should be deterministic, but updating the file contents will change hashes
|
||||
// be careful not to test that the HTML simply contains CSS, because it always will! filename and quanity matter here (bundling).
|
||||
const EXPECTED_CSS = {
|
||||
'/index.html': ['/_astro/common-', '/_astro/index-'], // don’t match hashes, which change based on content
|
||||
'/one/index.html': ['/_astro/common-', '/_astro/one/index-'],
|
||||
'/two/index.html': ['/_astro/common-', '/_astro/two/index-'],
|
||||
'/preload/index.html': ['/_astro/common-', '/_astro/preload/index-'],
|
||||
'/preload-merge/index.html': ['/_astro/preload-merge/index-'],
|
||||
'/index.html': ['assets/index', 'assets/typography'], // don’t match hashes, which change based on content
|
||||
'/one/index.html': ['../assets/one'],
|
||||
'/two/index.html': ['../assets/two', '../assets/typography'],
|
||||
'/preload/index.html': ['../assets/preload'],
|
||||
'/preload-merge/index.html': ['../assets/preload-merge'],
|
||||
};
|
||||
const UNEXPECTED_CSS = ['/_astro/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
|
||||
const UNEXPECTED_CSS = ['/src/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
|
||||
|
||||
let fixture;
|
||||
describe('CSS Bundling', function() {
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling/' });
|
||||
await fixture.build({ mode: 'production' });
|
||||
});
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling/' });
|
||||
await fixture.build({ mode: 'production' });
|
||||
});
|
||||
|
||||
describe('CSS Bundling', () => {
|
||||
it('Bundles CSS', async () => {
|
||||
const builtCSS = new Set();
|
||||
|
||||
|
@ -35,7 +33,8 @@ describe('CSS Bundling', () => {
|
|||
for (const href of css) {
|
||||
const link = $(`link[rel="stylesheet"][href^="${href}"]`);
|
||||
expect(link).to.have.lengthOf(1);
|
||||
builtCSS.add(link.attr('href'));
|
||||
const outHref = link.attr('href');
|
||||
builtCSS.add(outHref.startsWith('../') ? outHref.substr(2) : outHref);
|
||||
}
|
||||
|
||||
// test 2: assert old CSS was removed
|
||||
|
@ -46,8 +45,8 @@ describe('CSS Bundling', () => {
|
|||
|
||||
// test 3: preload tags was not removed and attributes was preserved
|
||||
if (filepath === '/preload/index.html') {
|
||||
const stylesheet = $('link[rel="stylesheet"][href^="/_astro/preload/index-"]');
|
||||
const preload = $('link[rel="preload"][href^="/_astro/preload/index-"]');
|
||||
const stylesheet = $('link[rel="stylesheet"][href^="../assets/preload"]');
|
||||
const preload = $('link[rel="preload"][href^="../assets/preload"]');
|
||||
expect(stylesheet[0].attribs.media).to.equal('print');
|
||||
expect(preload).to.have.lengthOf(1); // Preload tag was removed
|
||||
}
|
||||
|
@ -60,33 +59,9 @@ describe('CSS Bundling', () => {
|
|||
|
||||
// test 5: assert all bundled CSS was built and contains CSS
|
||||
for (const url of builtCSS.keys()) {
|
||||
const css = await context.readFile(url);
|
||||
const css = await fixture.readFile(url);
|
||||
expect(css).to.be.ok;
|
||||
}
|
||||
|
||||
// test 6: assert ordering is preserved (typography.css before colors.css)
|
||||
const bundledLoc = [...builtCSS].find((k) => k.startsWith('/_astro/common-'));
|
||||
const bundledContents = await context.readFile(bundledLoc);
|
||||
const typographyIndex = bundledContents.indexOf('body{');
|
||||
const colorsIndex = bundledContents.indexOf(':root{');
|
||||
expect(typographyIndex).toBeLessThan(colorsIndex);
|
||||
|
||||
// test 7: assert multiple style blocks were bundled (Nav.astro includes 2 scoped style blocks)
|
||||
const scopedNavStyles = [...bundledContents.matchAll('.nav.astro-')];
|
||||
expect(scopedNavStyles).to.have.lengthOf(2);
|
||||
|
||||
// test 8: assert <style global> was not scoped (in Nav.astro)
|
||||
const globalStyles = [...bundledContents.matchAll('html{')];
|
||||
expect(globalStyles).to.have.lengthOf(1);
|
||||
|
||||
// test 9: assert keyframes are only scoped for non-global styles (from Nav.astro)
|
||||
const scopedKeyframes = [...bundledContents.matchAll('nav-scoped-fade-astro')];
|
||||
const globalKeyframes = [...bundledContents.matchAll('nav-global-fade{')];
|
||||
expect(scopedKeyframes.length).toBeGreaterThan(0);
|
||||
expect(globalKeyframes.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it.skip('is skipped', () => {});
|
||||
});
|
|
@ -14,7 +14,7 @@ describe('Dynamic components', () => {
|
|||
const html = await fixture.readFile('/index.html');
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
expect($('script').length).to.eq(2);
|
||||
expect($('script').length).to.eq(1);
|
||||
});
|
||||
|
||||
it('Loads pages using client:media hydrator', async () => {
|
||||
|
@ -23,12 +23,10 @@ describe('Dynamic components', () => {
|
|||
const $ = cheerio.load(html);
|
||||
|
||||
// test 1: static value rendered
|
||||
|
||||
let js = await fixture.readFile(new URL($('script').attr('src'), root).pathname);
|
||||
expect(js).to.include(`value:"(max-width: 700px)"`);
|
||||
|
||||
// test 2: dynamic value rendered
|
||||
js = await fixture.readFile(new URL($('script').eq(1).attr('src'), root).pathname);
|
||||
expect(js).to.include(`value:"(max-width: 600px)"`);
|
||||
});
|
||||
|
||||
|
|
|
@ -48,10 +48,10 @@ describe('Astro.*', () => {
|
|||
expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/');
|
||||
});
|
||||
|
||||
it('Astro.resolve in development', async () => {
|
||||
it('Astro.resolve built', async () => {
|
||||
const html = await fixture.readFile('/resolve/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('img').attr('src')).to.include('/src/images/penguin.png');
|
||||
expect($('#inner-child img').attr('src')).to.include('/src/components/nested/images/penguin.png');
|
||||
expect($('img').attr('src')).to.include('assets/penguin.ccd44411.png'); // Main src/images
|
||||
expect($('#inner-child img').attr('src')).to.include('assets/penguin.b9ab122a.png');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,22 +2,17 @@ import { expect } from 'chai';
|
|||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-styles-ssr/' });
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
describe('Styles SSR', () => {
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-styles-ssr/' });
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Has <link> tags', async () => {
|
||||
const MUST_HAVE_LINK_TAGS = [
|
||||
'/src/components/ReactCSS.css',
|
||||
'/src/components/ReactModules.module.css',
|
||||
'/src/components/SvelteScoped.svelte',
|
||||
'/src/components/VueCSS.vue',
|
||||
'/src/components/VueModules.vue',
|
||||
'/src/components/VueScoped.vue',
|
||||
'assets/index'
|
||||
];
|
||||
|
||||
const html = await fixture.readFile('/index.html');
|
||||
|
@ -70,17 +65,19 @@ describe('Styles SSR', () => {
|
|||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const href = '/' + $('link').attr('href');
|
||||
const raw = await fixture.readFile(href);
|
||||
|
||||
let scopedClass;
|
||||
|
||||
// test 1: <style> tag in <head> is transformed
|
||||
const css = $('style')
|
||||
.html()
|
||||
const css = raw
|
||||
.replace(/\.astro-[A-Za-z0-9-]+/, (match) => {
|
||||
scopedClass = match; // get class hash from result
|
||||
return match;
|
||||
});
|
||||
|
||||
expect(css).to.equal(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px;}.outer${scopedClass}{color:red;}`);
|
||||
expect(css).to.include(`.wrapper${scopedClass}{margin-left:auto;margin-right:auto;max-width:1200px;}.outer${scopedClass}{color:red;}`);
|
||||
|
||||
// test 2: element received .astro-XXXXXX class (this selector will succeed if transformed correctly)
|
||||
const wrapper = $(`.wrapper${scopedClass}`);
|
||||
|
@ -110,10 +107,8 @@ describe('Styles SSR', () => {
|
|||
expect(el1.attr('class')).to.equal(`blue ${scopedClass}`);
|
||||
expect(el2.attr('class')).to.equal(`visible ${scopedClass}`);
|
||||
|
||||
let css = '';
|
||||
$('style').each((_, el) => {
|
||||
css += $(el).html();
|
||||
});
|
||||
const href = '/' + $('link').attr('href');
|
||||
const css = await fixture.readFile(href);
|
||||
|
||||
// test 4: CSS generates as expected
|
||||
expect(css).to.include(`.blue.${scopedClass}{color:powderblue;}.color\\:blue.${scopedClass}{color:powderblue;}.visible.${scopedClass}{display:block;}`);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</style>
|
||||
<body>
|
||||
<h1>Icons</h1>
|
||||
<img src="../images/twitter.png" srcset="../images/twitter.png 1x, ../images/twitter@2x.png 2x, ../images/twitter@3x.png 3x" />
|
||||
<img src={Astro.resolve('../images/twitter.png')} srcset={`${Astro.resolve('../images/twitter.png')} 1x, ${Astro.resolve('../images/twitter@2x.png')} 2x, ${Astro.resolve('../images/twitter@3x.png')} 3x`} />
|
||||
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 600w, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 800w">
|
||||
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 1.5x, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 2x">
|
||||
<!--
|
||||
|
|
|
@ -4,9 +4,9 @@ import Nav from '../components/Nav.astro';
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css/typography.css" />
|
||||
<link rel="stylesheet" href="../css/colors.css" />
|
||||
<link rel="stylesheet" href="../css/page-index.css" />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')}>
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/colors.css')}>
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-index.css')}>
|
||||
</head>
|
||||
<body>
|
||||
<Nav />
|
||||
|
|
|
@ -4,8 +4,8 @@ import Nav from '../components/Nav.astro';
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css/typography.css" />
|
||||
<link rel="stylesheet" href="../css/page-one.css" />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')} />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-one.css')} />
|
||||
</head>
|
||||
<body>
|
||||
<Nav />
|
||||
|
|
|
@ -4,11 +4,11 @@ import Nav from '../components/Nav.astro';
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preload" as="style" href="../css/page-preload-merge.css" />
|
||||
<link rel="preload" as="style" href="../css/page-preload-merge-2.css" />
|
||||
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge.css')} />
|
||||
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge-2.css')} />
|
||||
|
||||
<link rel="stylesheet" href="../css/page-preload-merge.css" />
|
||||
<link rel="stylesheet" href="../css/page-preload-merge-2.css" />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge.css')} />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge-2.css')} />
|
||||
</head>
|
||||
<body>
|
||||
<Nav />
|
||||
|
|
|
@ -4,8 +4,8 @@ import Nav from '../components/Nav.astro';
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="preload" href="../css/page-preload.css" />
|
||||
<link rel="stylesheet" href="../css/page-preload.css" media="print" onload="this.media='all'" />
|
||||
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload.css')} />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-preload.css')} media="print" onload="this.media='all'" />
|
||||
</head>
|
||||
<body>
|
||||
<Nav />
|
||||
|
|
|
@ -4,9 +4,9 @@ import Nav from '../components/Nav.astro';
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../css/typography.css" />
|
||||
<link rel="stylesheet" href="../css/colors.css" />
|
||||
<link rel="stylesheet" href="../css/page-two.css" />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')} />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/colors.css')} />
|
||||
<link rel="stylesheet" href={Astro.resolve('../css/page-two.css')} />
|
||||
</head>
|
||||
<body>
|
||||
<Nav />
|
||||
|
|
BIN
packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.png
vendored
Normal file
BIN
packages/astro/test/fixtures/astro-global/src/components/nested/images/penguin.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
packages/astro/test/fixtures/astro-global/src/images/penguin.png
vendored
Normal file
BIN
packages/astro/test/fixtures/astro-global/src/images/penguin.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 MiB |
125
yarn.lock
125
yarn.lock
|
@ -2214,16 +2214,6 @@
|
|||
"@types/parse5" "^6.0.1"
|
||||
parse5 "^6.0.1"
|
||||
|
||||
"@web/rollup-plugin-html@^1.10.1":
|
||||
version "1.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@web/rollup-plugin-html/-/rollup-plugin-html-1.10.1.tgz#7995d3aff436f6b5c1a365830a9ff525388b40d8"
|
||||
integrity sha512-XYJxHtdllwA5l4X8wh8CailrOykOl3YY+BRqO8+wS/I1Kq0JFISg3EUHdWAyVcw0TRDnHNLbOBJTm2ptAM+eog==
|
||||
dependencies:
|
||||
"@web/parse5-utils" "^1.3.0"
|
||||
glob "^7.1.6"
|
||||
html-minifier-terser "^6.0.0"
|
||||
parse5 "^6.0.1"
|
||||
|
||||
"@webcomponents/template-shadowroot@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@webcomponents/template-shadowroot/-/template-shadowroot-0.1.0.tgz#adb3438d0d9a18e8fced08abc253f56b7eadab00"
|
||||
|
@ -2901,14 +2891,6 @@ calmcard@~0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/calmcard/-/calmcard-0.1.1.tgz#35ac2b66492b0ed39ad06a893a0ff6e61124e449"
|
||||
integrity sha1-NawrZkkrDtOa0GqJOg/25hEk5Ek=
|
||||
|
||||
camel-case@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
|
||||
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
|
||||
dependencies:
|
||||
pascal-case "^3.1.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||
|
@ -3151,13 +3133,6 @@ ci-info@^3.2.0:
|
|||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6"
|
||||
integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==
|
||||
|
||||
clean-css@^5.1.5:
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.1.5.tgz#3b0af240dcfc9a3779a08c2332df3ebd4474f232"
|
||||
integrity sha512-9dr/cU/LjMpU57PXlSvDkVRh0rPxJBXiBtD0+SgYt8ahTCsXtfKjCkNYgIoTC6mBg8CFr5EKhW3DKCaGMUbUfQ==
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
|
@ -3330,20 +3305,15 @@ comma-separated-tokens@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
|
||||
integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==
|
||||
|
||||
commander@^2.20.0, commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^6.0.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
commander@^8.1.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8"
|
||||
integrity sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==
|
||||
commander@~2.20.3:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@~3.0.2:
|
||||
version "3.0.2"
|
||||
|
@ -3922,14 +3892,6 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0, domutils@^2.8.0:
|
|||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dot-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
dot-prop@^5.1.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
||||
|
@ -5655,7 +5617,7 @@ hastscript@^7.0.0:
|
|||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
|
||||
he@1.2.0, he@^1.2.0:
|
||||
he@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
@ -5697,19 +5659,6 @@ html-entities@2.3.2, html-entities@^2.3.2:
|
|||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488"
|
||||
integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==
|
||||
|
||||
html-minifier-terser@^6.0.0:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.0.2.tgz#14059ad64b69bf9f8b8a33f25b53411d8321e75d"
|
||||
integrity sha512-AgYO3UGhMYQx2S/FBJT3EM0ZYcKmH6m9XL9c1v77BeK/tYJxGPxT1/AtsdUi4FcP8kZGmqqnItCcjFPcX9hk6A==
|
||||
dependencies:
|
||||
camel-case "^4.1.2"
|
||||
clean-css "^5.1.5"
|
||||
commander "^8.1.0"
|
||||
he "^1.2.0"
|
||||
param-case "^3.0.4"
|
||||
relateurl "^0.2.7"
|
||||
terser "^5.7.2"
|
||||
|
||||
html-tags@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
|
||||
|
@ -6861,13 +6810,6 @@ loose-envify@^1.1.0:
|
|||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lower-case@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
|
||||
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
|
||||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
lru-cache@4.1.x, lru-cache@^4.0.1:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
|
@ -7797,14 +7739,6 @@ nlcst-to-string@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.4.tgz#9315dfab80882bbfd86ddf1b706f53622dc400cc"
|
||||
integrity sha512-3x3jwTd6UPG7vi5k4GEzvxJ5rDA7hVUIRNHPblKuMVP9Z3xmlsd9cgLcpAMkc5uPOBna82EeshROFhsPkbnTZg==
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
|
||||
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
|
||||
dependencies:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-emoji@^1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
|
||||
|
@ -8491,14 +8425,6 @@ pacote@^11.2.6:
|
|||
ssri "^8.0.1"
|
||||
tar "^6.1.0"
|
||||
|
||||
param-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
|
||||
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
|
||||
dependencies:
|
||||
dot-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
@ -8611,14 +8537,6 @@ parseurl@^1.3.2, parseurl@~1.3.3:
|
|||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
pascal-case@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
|
||||
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
path-browserify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
|
||||
|
@ -9348,11 +9266,6 @@ rehype-toc@^3.0.2:
|
|||
dependencies:
|
||||
"@jsdevtools/rehype-toc" "3.0.2"
|
||||
|
||||
relateurl@^0.2.7:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
|
||||
|
||||
remark-code-titles@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/remark-code-titles/-/remark-code-titles-0.1.2.tgz#ae41b47c517eae4084c761a59a60df5f0bd54aa8"
|
||||
|
@ -9935,25 +9848,17 @@ source-map-js@^0.6.2:
|
|||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
|
||||
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
|
||||
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.20"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
|
||||
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.5.0:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
|
||||
source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
||||
source-map@^0.6.1, source-map@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.3, source-map@~0.7.2:
|
||||
source-map@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
@ -10043,6 +9948,11 @@ sprintf-js@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
srcset-parse@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/srcset-parse/-/srcset-parse-1.1.0.tgz#73f787f38b73ede2c5af775e0a3465579488122b"
|
||||
integrity sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
|
@ -10448,15 +10358,6 @@ term-size@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
|
||||
integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
|
||||
|
||||
terser@^5.7.2:
|
||||
version "5.8.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.8.0.tgz#c6d352f91aed85cc6171ccb5e84655b77521d947"
|
||||
integrity sha512-f0JH+6yMpneYcRJN314lZrSwu9eKkUFEHLN/kNy8ceh8gaRiLgFPJqrB9HsXjhEGdv4e/ekjTOFxIlL6xlma8A==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.7.2"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-extensions@^1.0.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
|
||||
|
@ -10630,7 +10531,7 @@ tslib@^1.8.1, tslib@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.1, tslib@^2.0.3, tslib@^2.2.0:
|
||||
tslib@^2.0.1, tslib@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
|
||||
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
|
||||
|
|
Loading…
Reference in a new issue