Refactor: remove all legacy build logic from codebase (#3141)
* refactor: remove legacy build flag * refactor: remove legacy style maps * chore: changeset * refactor: nuke vite-plugin-build-html * deps: remove parse5 * tests: restore user provided doctype support! * deps: remove @web/parse5-utils * deps: change srcset-parse to dev dep * chore: remove unused utils * refactor: remove unused style mapping * unrelated fix: add .test to astro-markdown-css * refactor: remove unused astro-style with test update * chore: remove unused buildTime var
This commit is contained in:
parent
9e00f6d546
commit
0247b54270
23 changed files with 55 additions and 1086 deletions
5
.changeset/three-pigs-hunt.md
Normal file
5
.changeset/three-pigs-hunt.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Astro internals: remove all legacy build logic from the codebase, now that the legacy build flag has been removed
|
|
@ -86,7 +86,6 @@
|
|||
"@babel/traverse": "^7.17.9",
|
||||
"@proload/core": "^0.2.2",
|
||||
"@proload/plugin-tsm": "^0.1.1",
|
||||
"@web/parse5-utils": "^1.3.0",
|
||||
"ast-types": "^0.14.2",
|
||||
"boxen": "^6.2.1",
|
||||
"ci-info": "^3.3.0",
|
||||
|
@ -109,7 +108,6 @@
|
|||
"micromorph": "^0.1.2",
|
||||
"mime": "^3.0.0",
|
||||
"ora": "^6.1.0",
|
||||
"parse5": "^6.0.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"postcss": "^8.4.12",
|
||||
|
@ -128,7 +126,6 @@
|
|||
"sirv": "^2.0.2",
|
||||
"slash": "^4.0.0",
|
||||
"sourcemap-codec": "^1.4.8",
|
||||
"srcset-parse": "^1.1.0",
|
||||
"string-width": "^5.1.2",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"supports-esm": "^1.0.0",
|
||||
|
@ -163,7 +160,8 @@
|
|||
"chai": "^4.3.6",
|
||||
"cheerio": "^1.0.0-rc.10",
|
||||
"mocha": "^9.2.2",
|
||||
"sass": "^1.50.0"
|
||||
"sass": "^1.50.0",
|
||||
"srcset-parse": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || >=16.0.0",
|
||||
|
|
|
@ -983,7 +983,6 @@ export interface SSRElement {
|
|||
export interface SSRMetadata {
|
||||
renderers: SSRLoadedRenderer[];
|
||||
pathname: string;
|
||||
legacyBuild: boolean;
|
||||
}
|
||||
|
||||
export interface SSRResult {
|
||||
|
|
|
@ -48,7 +48,6 @@ function printAstroHelp() {
|
|||
['--host [optional IP]', 'Expose server on network'],
|
||||
['--config <path>', 'Specify the path to the Astro config file.'],
|
||||
['--root <path>', 'Specify the path to the project root folder.'],
|
||||
['--legacy-build', 'Use the build strategy prior to 0.24.0'],
|
||||
['--drafts', 'Include markdown draft pages in the build.'],
|
||||
['--verbose', 'Enable verbose logging'],
|
||||
['--silent', 'Disable logging'],
|
||||
|
|
|
@ -78,7 +78,6 @@ export class App {
|
|||
const scripts = createModuleScriptElementWithSrcSet(info.scripts, manifest.site);
|
||||
|
||||
const result = await render({
|
||||
legacyBuild: false,
|
||||
links,
|
||||
logging: this.#logging,
|
||||
markdown: manifest.markdown,
|
||||
|
|
|
@ -193,7 +193,6 @@ async function generatePath(
|
|||
const ssr = isBuildingToSSR(opts.astroConfig);
|
||||
const url = new URL(opts.astroConfig.base + removeLeadingForwardSlash(pathname), origin);
|
||||
const options: RenderOptions = {
|
||||
legacyBuild: false,
|
||||
links,
|
||||
logging,
|
||||
markdown: astroConfig.markdown,
|
||||
|
|
|
@ -213,7 +213,6 @@ class AstroBuilder {
|
|||
pageCount: number;
|
||||
buildMode: 'static' | 'ssr';
|
||||
}) {
|
||||
const buildTime = performance.now() - timeStart;
|
||||
const total = getTimeStat(timeStart, performance.now());
|
||||
|
||||
let messages: string[] = [];
|
||||
|
|
|
@ -30,24 +30,6 @@ export interface BuildInternals {
|
|||
* A map for page-specific information by a client:only component
|
||||
*/
|
||||
pagesByClientOnly: Map<string, Set<PageBuildData>>;
|
||||
|
||||
/**
|
||||
* chunkToReferenceIdMap maps them to a hash id used to find the final file.
|
||||
* @deprecated This Map is only used for the legacy build.
|
||||
*/
|
||||
chunkToReferenceIdMap: Map<string, string>;
|
||||
|
||||
/**
|
||||
* This is a mapping of pathname to the string source of all collected inline <style> for a page.
|
||||
* @deprecated This Map is only used for the legacy build.
|
||||
*/
|
||||
astroStyleMap: Map<string, string>;
|
||||
|
||||
/**
|
||||
* This is a virtual JS module that imports all dependent styles for a page.
|
||||
* @deprecated This Map is only used for the legacy build.
|
||||
*/
|
||||
astroPageStyleMap: Map<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,9 +53,6 @@ export function createBuildInternals(): BuildInternals {
|
|||
|
||||
return {
|
||||
pureCSSChunks,
|
||||
chunkToReferenceIdMap,
|
||||
astroStyleMap,
|
||||
astroPageStyleMap,
|
||||
hoistedScriptIdToHoistedMap,
|
||||
entrySpecifierToBundleMap: new Map<string, string>(),
|
||||
|
||||
|
|
|
@ -151,7 +151,6 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
|||
vitePluginPages(opts, internals),
|
||||
rollupPluginAstroBuildCSS({
|
||||
internals,
|
||||
legacy: false,
|
||||
target: 'server',
|
||||
}),
|
||||
...(viteConfig.plugins || []),
|
||||
|
@ -222,7 +221,6 @@ async function clientBuild(
|
|||
vitePluginHoistedScripts(astroConfig, internals),
|
||||
rollupPluginAstroBuildCSS({
|
||||
internals,
|
||||
legacy: false,
|
||||
target: 'client',
|
||||
}),
|
||||
...(viteConfig.plugins || []),
|
||||
|
|
|
@ -38,11 +38,3 @@ export function startsWithDotSlash(path: string) {
|
|||
export function isRelativePath(path: string) {
|
||||
return startsWithDotDotSlash(path) || startsWithDotSlash(path);
|
||||
}
|
||||
|
||||
export function prependDotSlash(path: string) {
|
||||
if (isRelativePath(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
return './' + path;
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ export async function getParamsAndProps(
|
|||
}
|
||||
|
||||
export interface RenderOptions {
|
||||
legacyBuild: boolean;
|
||||
logging: LogOptions;
|
||||
links: Set<SSRElement>;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
|
@ -87,7 +86,6 @@ export async function render(
|
|||
opts: RenderOptions
|
||||
): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> {
|
||||
const {
|
||||
legacyBuild,
|
||||
links,
|
||||
logging,
|
||||
origin,
|
||||
|
@ -128,7 +126,6 @@ export async function render(
|
|||
throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
|
||||
|
||||
const result = createResult({
|
||||
legacyBuild,
|
||||
links,
|
||||
logging,
|
||||
markdown,
|
||||
|
|
|
@ -102,18 +102,13 @@ export async function render(
|
|||
routeCache,
|
||||
viteServer,
|
||||
} = ssrOpts;
|
||||
// TODO: clean up "legacy" flag passed through helper functions
|
||||
const isLegacyBuild = false;
|
||||
|
||||
// Add hoisted script tags
|
||||
const scripts = createModuleScriptElementWithSrcSet(
|
||||
!isLegacyBuild && mod.hasOwnProperty('$$metadata')
|
||||
? Array.from(mod.$$metadata.hoistedScriptPaths())
|
||||
: []
|
||||
mod.hasOwnProperty('$$metadata') ? Array.from(mod.$$metadata.hoistedScriptPaths()) : []
|
||||
);
|
||||
|
||||
// Inject HMR scripts
|
||||
if (mod.hasOwnProperty('$$metadata') && mode === 'development' && !isLegacyBuild) {
|
||||
if (mod.hasOwnProperty('$$metadata') && mode === 'development') {
|
||||
scripts.add({
|
||||
props: { type: 'module', src: '/@vite/client' },
|
||||
children: '',
|
||||
|
@ -138,29 +133,25 @@ export async function render(
|
|||
|
||||
// Pass framework CSS in as link tags to be appended to the page.
|
||||
let links = new Set<SSRElement>();
|
||||
if (!isLegacyBuild) {
|
||||
[...(await getStylesForURL(filePath, viteServer))].forEach((href) => {
|
||||
if (mode === 'development' && svelteStylesRE.test(href)) {
|
||||
scripts.add({
|
||||
props: { type: 'module', src: href },
|
||||
children: '',
|
||||
});
|
||||
} else {
|
||||
links.add({
|
||||
props: {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-astro-injected': true,
|
||||
},
|
||||
children: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
[...(await getStylesForURL(filePath, viteServer))].forEach((href) => {
|
||||
if (mode === 'development' && svelteStylesRE.test(href)) {
|
||||
scripts.add({
|
||||
props: { type: 'module', src: href },
|
||||
children: '',
|
||||
});
|
||||
} else {
|
||||
links.add({
|
||||
props: {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-astro-injected': true,
|
||||
},
|
||||
children: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let content = await coreRender({
|
||||
// TODO: Remove this flag once legacyBuild support is removed
|
||||
legacyBuild: isLegacyBuild,
|
||||
links,
|
||||
logging,
|
||||
markdown: astroConfig.markdown,
|
||||
|
@ -197,41 +188,6 @@ export async function render(
|
|||
// inject tags
|
||||
const tags: vite.HtmlTagDescriptor[] = [];
|
||||
|
||||
// dev only: inject Astro HMR client
|
||||
if (mode === 'development' && isLegacyBuild) {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
attrs: { type: 'module' },
|
||||
// HACK: inject the direct contents of our `astro/runtime/client/hmr.js` to ensure
|
||||
// `import.meta.hot` is properly handled by Vite
|
||||
children: await getHmrScript(),
|
||||
injectTo: 'head',
|
||||
});
|
||||
}
|
||||
|
||||
// inject CSS
|
||||
if (isLegacyBuild) {
|
||||
[...(await getStylesForURL(filePath, viteServer))].forEach((href) => {
|
||||
if (mode === 'development' && svelteStylesRE.test(href)) {
|
||||
tags.push({
|
||||
tag: 'script',
|
||||
attrs: { type: 'module', src: href },
|
||||
injectTo: 'head',
|
||||
});
|
||||
} else {
|
||||
tags.push({
|
||||
tag: 'link',
|
||||
attrs: {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-astro-injected': true,
|
||||
},
|
||||
injectTo: 'head',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// add injected tags
|
||||
let html = injectTags(content.html, tags);
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ function onlyAvailableInSSR(name: string) {
|
|||
|
||||
export interface CreateResultArgs {
|
||||
ssr: boolean;
|
||||
legacyBuild: boolean;
|
||||
logging: LogOptions;
|
||||
origin: string;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
|
@ -101,7 +100,7 @@ class Slots {
|
|||
let renderMarkdown: any = null;
|
||||
|
||||
export function createResult(args: CreateResultArgs): SSRResult {
|
||||
const { legacyBuild, markdown, params, pathname, renderers, request, resolve, site } = args;
|
||||
const { markdown, params, pathname, renderers, request, resolve, site } = args;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const canonicalURL = createCanonicalURL('.' + pathname, site ?? url.origin);
|
||||
|
@ -138,16 +137,15 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
}
|
||||
: onlyAvailableInSSR('Astro.redirect'),
|
||||
resolve(path: string) {
|
||||
if (!legacyBuild) {
|
||||
let extra = `This can be replaced with a dynamic import like so: await import("${path}")`;
|
||||
if (isCSSRequest(path)) {
|
||||
extra = `It looks like you are resolving styles. If you are adding a link tag, replace with this:
|
||||
let extra = `This can be replaced with a dynamic import like so: await import("${path}")`;
|
||||
if (isCSSRequest(path)) {
|
||||
extra = `It looks like you are resolving styles. If you are adding a link tag, replace with this:
|
||||
---
|
||||
import "${path}";
|
||||
---
|
||||
`;
|
||||
} else if (isScriptRequest(path)) {
|
||||
extra = `It looks like you are resolving scripts. If you are adding a script tag, replace with this:
|
||||
} else if (isScriptRequest(path)) {
|
||||
extra = `It looks like you are resolving scripts. If you are adding a script tag, replace with this:
|
||||
|
||||
<script type="module" src={(await import("${path}?url")).default}></script>
|
||||
|
||||
|
@ -157,21 +155,18 @@ or consider make it a module like so:
|
|||
import MyModule from "${path}";
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
|
||||
warn(
|
||||
args.logging,
|
||||
`deprecation`,
|
||||
`${bold(
|
||||
'Astro.resolve()'
|
||||
)} is deprecated. We see that you are trying to resolve ${path}.
|
||||
${extra}`
|
||||
);
|
||||
// Intentionally return an empty string so that it is not relied upon.
|
||||
return '';
|
||||
}
|
||||
|
||||
return astroGlobal.resolve(path);
|
||||
warn(
|
||||
args.logging,
|
||||
`deprecation`,
|
||||
`${bold(
|
||||
'Astro.resolve()'
|
||||
)} is deprecated. We see that you are trying to resolve ${path}.
|
||||
${extra}`
|
||||
);
|
||||
// Intentionally return an empty string so that it is not relied upon.
|
||||
return '';
|
||||
},
|
||||
slots: astroSlots,
|
||||
} as unknown as AstroGlobal;
|
||||
|
@ -206,7 +201,6 @@ ${extra}`
|
|||
_metadata: {
|
||||
renderers,
|
||||
pathname,
|
||||
legacyBuild,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -543,16 +543,7 @@ const uniqueElements = (item: any, index: number, all: any[]) => {
|
|||
// Renders a page to completion by first calling the factory callback, waiting for its result, and then appending
|
||||
// styles and scripts into the head.
|
||||
export async function renderHead(result: SSRResult): Promise<string> {
|
||||
const styles = Array.from(result.styles)
|
||||
.filter(uniqueElements)
|
||||
.map((style) => {
|
||||
// TODO: clean up legacyBuild from metadata
|
||||
const styleChildren = !result._metadata.legacyBuild ? '' : style.children;
|
||||
return renderElement('style', {
|
||||
children: styleChildren,
|
||||
props: { ...style.props, 'astro-style': true },
|
||||
});
|
||||
});
|
||||
const styles = [];
|
||||
let needsHydrationStyles = false;
|
||||
const scripts = Array.from(result.scripts)
|
||||
.filter(uniqueElements)
|
||||
|
@ -568,7 +559,7 @@ export async function renderHead(result: SSRResult): Promise<string> {
|
|||
if (needsHydrationStyles) {
|
||||
styles.push(
|
||||
renderElement('style', {
|
||||
props: { 'astro-style': true },
|
||||
props: {},
|
||||
children: 'astro-root, astro-fragment { display: contents; }',
|
||||
})
|
||||
);
|
||||
|
|
|
@ -19,27 +19,6 @@ const ASTRO_STYLE_PREFIX = '@astro-inline-style';
|
|||
|
||||
const ASTRO_PAGE_STYLE_PREFIX = '@astro-page-all-styles';
|
||||
|
||||
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';
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -54,12 +33,11 @@ function isRawOrUrlModule(id: string) {
|
|||
|
||||
interface PluginOptions {
|
||||
internals: BuildInternals;
|
||||
legacy: boolean;
|
||||
target: 'client' | 'server';
|
||||
}
|
||||
|
||||
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||
const { internals, legacy } = options;
|
||||
const { internals } = options;
|
||||
const styleSourceMap = new Map<string, string>();
|
||||
|
||||
function* walkStyles(
|
||||
|
@ -153,16 +131,6 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
return undefined;
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
if (isPageStyleVirtualModule(id)) {
|
||||
return internals.astroPageStyleMap.get(id) || null;
|
||||
}
|
||||
if (isStyleVirtualModule(id)) {
|
||||
return internals.astroStyleMap.get(id) || null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async transform(value, id) {
|
||||
if (isStyleVirtualModule(id)) {
|
||||
styleSourceMap.set(id, value);
|
||||
|
@ -203,7 +171,6 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
source: minifiedCSS,
|
||||
});
|
||||
|
||||
internals.chunkToReferenceIdMap.set(chunk.fileName, referenceId);
|
||||
if (chunk.type === 'chunk') {
|
||||
const fileName = this.getFileName(referenceId);
|
||||
for (const pageData of getPageDatasByChunk(internals, chunk)) {
|
||||
|
@ -236,26 +203,10 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
);
|
||||
|
||||
// Crawl the module graph to find CSS chunks to create
|
||||
if (!legacy) {
|
||||
await addStyles.call(this);
|
||||
}
|
||||
await addStyles.call(this);
|
||||
|
||||
for (const [chunkId, chunk] of Object.entries(bundle)) {
|
||||
if (chunk.type === 'chunk') {
|
||||
// This find shared chunks of CSS and adds them to the main CSS chunks,
|
||||
// so that shared CSS is added to the page.
|
||||
for (const { css: cssSet } of getPageDatasByChunk(internals, chunk)) {
|
||||
for (const imp of chunk.imports) {
|
||||
if (internals.chunkToReferenceIdMap.has(imp) && !pureChunkFilenames.has(imp)) {
|
||||
const referenceId = internals.chunkToReferenceIdMap.get(imp)!;
|
||||
const fileName = this.getFileName(referenceId);
|
||||
if (!cssSet.has(fileName)) {
|
||||
cssSet.add(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removes imports for pure CSS chunks.
|
||||
if (hasPureCSSChunks) {
|
||||
if (internals.pureCSSChunks.has(chunk) && !chunk.exports.length) {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
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.`);
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
import { Document, Element, Node } from 'parse5';
|
||||
import npath from 'path';
|
||||
import { findElements, getTagName, getAttribute, findNodes, hasAttribute } 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'];
|
||||
const windowsPathRE = /^[A-Z]:\//;
|
||||
|
||||
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;
|
||||
}
|
||||
// Windows fix: if path starts with C:/, avoid URL parsing
|
||||
if (windowsPathRE.test(path)) {
|
||||
return true;
|
||||
}
|
||||
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 isExternalScript(node: Element): boolean {
|
||||
switch (getTagName(node)) {
|
||||
case 'script':
|
||||
if (hasAttribute(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 getAttributes(node: Element): Record<string, any> {
|
||||
return Object.fromEntries(node.attrs.map((attr) => [attr.name, attr.value]));
|
||||
}
|
||||
|
||||
export function findAssets(document: Document) {
|
||||
return findElements(document, isAsset);
|
||||
}
|
||||
|
||||
export function findInlineScripts(document: Document) {
|
||||
return findElements(document, isInlineScript);
|
||||
}
|
||||
|
||||
export function findExternalScripts(document: Document) {
|
||||
return findElements(document, isExternalScript);
|
||||
}
|
||||
|
||||
export function findInlineStyles(document: Document) {
|
||||
return findElements(document, isInlineStyle);
|
||||
}
|
||||
|
||||
export function findStyleLinks(document: Document) {
|
||||
return findElements(document, isStylesheetLink);
|
||||
}
|
|
@ -1,537 +0,0 @@
|
|||
import {
|
||||
createElement,
|
||||
createScript,
|
||||
getAttribute,
|
||||
hasAttribute,
|
||||
insertBefore,
|
||||
remove,
|
||||
setAttribute,
|
||||
} from '@web/parse5-utils';
|
||||
import { promises as fs } from 'fs';
|
||||
import parse5 from 'parse5';
|
||||
import * as npath from 'path';
|
||||
import type { OutputChunk, PluginContext, PreRenderedChunk } from 'rollup';
|
||||
import srcsetParse from 'srcset-parse';
|
||||
import type { Plugin as VitePlugin, ViteDevServer } from 'vite';
|
||||
import type { AstroConfig } from '../@types/astro';
|
||||
import type { BuildInternals } from '../core/build/internal';
|
||||
import type { AllPagesData } from '../core/build/types';
|
||||
import type { LogOptions } from '../core/logger/core.js';
|
||||
import { prependDotSlash } from '../core/path.js';
|
||||
import { render as ssrRender } from '../core/render/dev/index.js';
|
||||
import { RouteCache } from '../core/render/route-cache.js';
|
||||
import { getOutputFilename } from '../core/util.js';
|
||||
import { getAstroPageStyleId, getAstroStyleId } from '../vite-plugin-build-css/index.js';
|
||||
import { addRollupInput } from './add-rollup-input.js';
|
||||
import {
|
||||
findAssets,
|
||||
findExternalScripts,
|
||||
findInlineScripts,
|
||||
findInlineStyles,
|
||||
getAttributes,
|
||||
getTextContent,
|
||||
} from './extract-assets.js';
|
||||
import {
|
||||
hasSrcSet,
|
||||
isBuildableImage,
|
||||
isBuildableLink,
|
||||
isHoistedScript,
|
||||
isInSrcDirectory,
|
||||
} from './util.js';
|
||||
import { createRequest } from '../core/request.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';
|
||||
|
||||
interface PluginOptions {
|
||||
astroConfig: AstroConfig;
|
||||
internals: BuildInternals;
|
||||
logging: LogOptions;
|
||||
allPages: AllPagesData;
|
||||
pageNames: string[];
|
||||
origin: string;
|
||||
routeCache: RouteCache;
|
||||
viteServer: ViteDevServer;
|
||||
}
|
||||
|
||||
function relativePath(from: string, to: string): string {
|
||||
const rel = npath.posix.relative(from, to);
|
||||
return prependDotSlash(rel);
|
||||
}
|
||||
|
||||
export function rollupPluginAstroScanHTML(options: PluginOptions): VitePlugin {
|
||||
const { astroConfig, internals, logging, origin, allPages, routeCache, viteServer, pageNames } =
|
||||
options;
|
||||
|
||||
// The filepath root of the src folder
|
||||
const srcRoot = astroConfig.srcDir.pathname;
|
||||
// The web path of the src folter
|
||||
const srcRootWeb = srcRoot.substr(astroConfig.root.pathname.length - 1);
|
||||
|
||||
// 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[]>();
|
||||
const pageStyleImportOrder: string[] = [];
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
|
||||
enforce: 'pre',
|
||||
|
||||
async options(inputOptions) {
|
||||
const htmlInput: Set<string> = new Set();
|
||||
const assetInput: Set<string> = new Set();
|
||||
const jsInput: Set<string> = new Set();
|
||||
|
||||
for (const [component, pageData] of Object.entries(allPages)) {
|
||||
const [renderers, mod] = pageData.preload;
|
||||
|
||||
// Hydrated components are statically identified.
|
||||
for (const path of mod.$$metadata.hydratedComponentPaths()) {
|
||||
jsInput.add(path);
|
||||
}
|
||||
|
||||
for (const pathname of pageData.paths) {
|
||||
pageNames.push(pathname.replace(/\/?$/, '/').replace(/^\//, ''));
|
||||
const id = ASTRO_PAGE_PREFIX + pathname;
|
||||
const response = await ssrRender(renderers, mod, {
|
||||
astroConfig,
|
||||
filePath: new URL(`./${component}`, astroConfig.root),
|
||||
logging,
|
||||
request: createRequest({
|
||||
url: new URL(origin + pathname),
|
||||
headers: new Headers(),
|
||||
logging,
|
||||
ssr: false,
|
||||
}),
|
||||
mode: 'production',
|
||||
origin,
|
||||
pathname,
|
||||
route: pageData.route,
|
||||
routeCache,
|
||||
viteServer,
|
||||
});
|
||||
|
||||
if (response.type !== 'html') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const html = response.html;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for (const script of findExternalScripts(document)) {
|
||||
if (isHoistedScript(script)) {
|
||||
const astroScript = getAttribute(script, 'astro-script');
|
||||
const src = getAttribute(script, 'src');
|
||||
if (astroScript) {
|
||||
const js = `import '${src}';`;
|
||||
const scriptId = ASTRO_SCRIPT_PREFIX + astroScript;
|
||||
frontEndImports.push(scriptId);
|
||||
astroScriptMap.set(scriptId, js);
|
||||
}
|
||||
} else if (isInSrcDirectory(script, 'src', srcRoot, srcRootWeb)) {
|
||||
const src = getAttribute(script, 'src');
|
||||
if (src) jsInput.add(src);
|
||||
}
|
||||
}
|
||||
|
||||
const assetImports = [];
|
||||
const styleId = getAstroStyleId(pathname);
|
||||
let styles = 0;
|
||||
for (const node of findInlineStyles(document)) {
|
||||
if (hasAttribute(node, 'astro-style')) {
|
||||
const style = getTextContent(node) || ' '; // If an empty node, add whitespace
|
||||
const thisStyleId = `${styleId}/${++styles}.css`;
|
||||
internals.astroStyleMap.set(thisStyleId, style);
|
||||
assetImports.push(thisStyleId);
|
||||
}
|
||||
}
|
||||
|
||||
for (let node of findAssets(document)) {
|
||||
if (isBuildableLink(node, srcRoot, srcRootWeb)) {
|
||||
const href = getAttribute(node, 'href')!;
|
||||
assetImports.push(href);
|
||||
}
|
||||
|
||||
if (isBuildableImage(node, srcRoot, srcRootWeb)) {
|
||||
const src = getAttribute(node, 'src');
|
||||
if (src?.startsWith(srcRoot) && !astroAssetMap.has(src)) {
|
||||
astroAssetMap.set(src, fs.readFile(new URL(`file://${src}`)));
|
||||
} else if (src?.startsWith(srcRootWeb) && !astroAssetMap.has(src)) {
|
||||
const resolved = new URL('.' + src, astroConfig.root);
|
||||
astroAssetMap.set(src, fs.readFile(resolved));
|
||||
}
|
||||
}
|
||||
|
||||
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}`)));
|
||||
} else if (url.startsWith(srcRootWeb) && !astroAssetMap.has(url)) {
|
||||
const resolved = new URL('.' + url, astroConfig.root);
|
||||
astroAssetMap.set(url, fs.readFile(resolved));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
internals.astroPageStyleMap.set(pageStyleId, jsSource);
|
||||
assetInput.add(pageStyleId);
|
||||
|
||||
// preserve asset order in the order we encounter them
|
||||
for (const assetHref of assetImports) {
|
||||
if (!pageStyleImportOrder.includes(assetHref)) pageStyleImportOrder.push(assetHref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(this: PluginContext, _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);
|
||||
}
|
||||
|
||||
// Sort CSS in order of appearance in HTML (pageStyleImportOrder)
|
||||
// This is the “global ordering” used below
|
||||
const sortedCSSChunks = [...internals.pureCSSChunks];
|
||||
sortedCSSChunks.sort((a, b) => {
|
||||
let aIndex = Math.min(
|
||||
...Object.keys(a.modules).map((id) => {
|
||||
const i = pageStyleImportOrder.findIndex((url) => id.endsWith(url));
|
||||
return i >= 0 ? i : Infinity; // if -1 is encountered (unknown order), move to the end (Infinity)
|
||||
})
|
||||
);
|
||||
let bIndex = Math.min(
|
||||
...Object.keys(b.modules).map((id) => {
|
||||
const i = pageStyleImportOrder.findIndex((url) => id.endsWith(url));
|
||||
return i >= 0 ? i : Infinity;
|
||||
})
|
||||
);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
const sortedChunkNames = sortedCSSChunks.map(({ fileName }) => fileName);
|
||||
|
||||
// Create a mapping of chunks to dependent chunks, used to add the proper
|
||||
// link tags for CSS.
|
||||
for (const chunk of sortedCSSChunks) {
|
||||
const chunkModules = [chunk.fileName, ...chunk.imports];
|
||||
// For each HTML output, sort CSS in HTML order Note: here we actually
|
||||
// want -1 to be first. Since the last CSS “wins”, we want to load
|
||||
// “unknown” (-1) CSS ordering first, followed by “known” ordering at
|
||||
// the end so it takes priority
|
||||
chunkModules.sort((a, b) => sortedChunkNames.indexOf(a) - sortedChunkNames.indexOf(b));
|
||||
|
||||
const referenceIDs: string[] = [];
|
||||
for (const chunkID of chunkModules) {
|
||||
const referenceID = internals.chunkToReferenceIdMap.get(chunkID);
|
||||
if (referenceID) referenceIDs.push(referenceID);
|
||||
}
|
||||
for (const id of Object.keys(chunk.modules)) {
|
||||
cssChunkMap.set(id, referenceIDs);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = relativePath(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,
|
||||
});
|
||||
|
||||
// This is the module for the page-level bundle which includes
|
||||
// hoisted scripts and hydrated components.
|
||||
const pageAssetId = facadeIdMap.get(id);
|
||||
const bundlePath = '/' + pageAssetId;
|
||||
|
||||
// Update scripts
|
||||
let pageBundleAdded = false;
|
||||
|
||||
// Update inline scripts. These could be hydrated component scripts or hoisted inline scripts
|
||||
for (let script of findInlineScripts(document)) {
|
||||
if (getAttribute(script, 'astro-script') && typeof pageAssetId === 'string') {
|
||||
if (!pageBundleAdded) {
|
||||
pageBundleAdded = true;
|
||||
const relPath = relativePath(pathname, bundlePath);
|
||||
insertBefore(
|
||||
script.parentNode,
|
||||
createScript({
|
||||
type: 'module',
|
||||
src: relPath,
|
||||
}),
|
||||
script
|
||||
);
|
||||
}
|
||||
remove(script);
|
||||
}
|
||||
}
|
||||
|
||||
// Update external scripts. These could be hoisted or in the src folder.
|
||||
for (let script of findExternalScripts(document)) {
|
||||
if (getAttribute(script, 'astro-script') && typeof pageAssetId === 'string') {
|
||||
if (!pageBundleAdded) {
|
||||
pageBundleAdded = true;
|
||||
const relPath = relativePath(pathname, bundlePath);
|
||||
insertBefore(
|
||||
script.parentNode,
|
||||
createScript({
|
||||
type: 'module',
|
||||
src: relPath,
|
||||
}),
|
||||
script
|
||||
);
|
||||
}
|
||||
remove(script);
|
||||
} else if (isInSrcDirectory(script, 'src', srcRoot, srcRootWeb)) {
|
||||
let src = getAttribute(script, 'src');
|
||||
// If this is projectRoot relative, get the fullpath to match the facadeId.
|
||||
if (src?.startsWith(srcRootWeb)) {
|
||||
src = new URL('.' + src, astroConfig.root).pathname;
|
||||
}
|
||||
// On windows the facadeId doesn't start with / but does not Unix :/
|
||||
if (src && (facadeIdMap.has(src) || facadeIdMap.has(src.substr(1)))) {
|
||||
const assetRootPath = '/' + (facadeIdMap.get(src) || facadeIdMap.get(src.substr(1)));
|
||||
const relPath = relativePath(pathname, assetRootPath);
|
||||
const attrs = getAttributes(script);
|
||||
insertBefore(
|
||||
script.parentNode,
|
||||
createScript({
|
||||
...attrs,
|
||||
src: relPath,
|
||||
}),
|
||||
script
|
||||
);
|
||||
remove(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styleId = getAstroPageStyleId(pathname);
|
||||
let pageCSSAdded = false;
|
||||
for (const node of findAssets(document)) {
|
||||
if (isBuildableLink(node, srcRoot, srcRootWeb)) {
|
||||
const rel = getAttribute(node, 'rel');
|
||||
switch (rel) {
|
||||
case 'stylesheet': {
|
||||
if (!pageCSSAdded) {
|
||||
const attrs = getAttributes(node);
|
||||
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 = getAttributes(node);
|
||||
appendStyleChunksBefore(node, pathname, cssChunkMap.get(styleId), attrs);
|
||||
remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBuildableImage(node, srcRoot, srcRootWeb)) {
|
||||
const src = getAttribute(node, 'src')!;
|
||||
const referenceId = assetIdMap.get(src);
|
||||
if (referenceId) {
|
||||
const fileName = this.getFileName(referenceId);
|
||||
const relPath = relativePath(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 = relativePath(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 (hasAttribute(style, 'astro-style')) {
|
||||
if (!pageCSSAdded) {
|
||||
pageCSSAdded = appendStyleChunksBefore(style, pathname, cssChunkMap.get(styleId));
|
||||
}
|
||||
|
||||
remove(style);
|
||||
}
|
||||
}
|
||||
|
||||
const outHTML = parse5.serialize(document);
|
||||
const name = pathname.substr(1);
|
||||
const outPath = getOutputFilename(astroConfig, name);
|
||||
|
||||
this.emitFile({
|
||||
fileName: outPath,
|
||||
source: outHTML,
|
||||
type: 'asset',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
import { getAttribute, hasAttribute, getTagName } from '@web/parse5-utils';
|
||||
import parse5 from 'parse5';
|
||||
import { isStylesheetLink } from './extract-assets.js';
|
||||
|
||||
const tagsWithSrcSet = new Set(['img', 'source']);
|
||||
|
||||
function startsWithSrcRoot(pathname: string, srcRoot: string, srcRootWeb: string): boolean {
|
||||
return (
|
||||
pathname.startsWith(srcRoot) || // /Users/user/project/src/styles/main.css
|
||||
pathname.startsWith(srcRootWeb) || // /src/styles/main.css
|
||||
`/${pathname}`.startsWith(srcRoot)
|
||||
); // Windows fix: some paths are missing leading "/"
|
||||
}
|
||||
|
||||
export function isInSrcDirectory(
|
||||
node: parse5.Element,
|
||||
attr: string,
|
||||
srcRoot: string,
|
||||
srcRootWeb: string
|
||||
): boolean {
|
||||
const value = getAttribute(node, attr);
|
||||
return value ? startsWithSrcRoot(value, srcRoot, srcRootWeb) : false;
|
||||
}
|
||||
|
||||
export function isAstroInjectedLink(node: parse5.Element): boolean {
|
||||
return isStylesheetLink(node) && getAttribute(node, 'data-astro-injected') === '';
|
||||
}
|
||||
|
||||
export function isBuildableLink(
|
||||
node: parse5.Element,
|
||||
srcRoot: string,
|
||||
srcRootWeb: string
|
||||
): boolean {
|
||||
if (isAstroInjectedLink(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const href = getAttribute(node, 'href');
|
||||
if (typeof href !== 'string' || !href.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return startsWithSrcRoot(href, srcRoot, srcRootWeb);
|
||||
}
|
||||
|
||||
export function isBuildableImage(
|
||||
node: parse5.Element,
|
||||
srcRoot: string,
|
||||
srcRootWeb: string
|
||||
): boolean {
|
||||
if (getTagName(node) === 'img') {
|
||||
const src = getAttribute(node, 'src');
|
||||
return src ? startsWithSrcRoot(src, srcRoot, srcRootWeb) : false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasSrcSet(node: parse5.Element): boolean {
|
||||
return tagsWithSrcSet.has(getTagName(node)) && !!getAttribute(node, 'srcset');
|
||||
}
|
||||
|
||||
export function isHoistedScript(node: parse5.Element): boolean {
|
||||
return getTagName(node) === 'script' && hasAttribute(node, 'hoist');
|
||||
}
|
|
@ -291,8 +291,8 @@ describe('CSS', function () {
|
|||
});
|
||||
|
||||
it('resolves Astro styles', async () => {
|
||||
const style = $('style[astro-style]');
|
||||
expect(style.length).to.not.equal(0);
|
||||
const astroPageCss = $('link[rel=stylesheet][href^=/src/pages/index.astro?astro&type=style]');
|
||||
expect(astroPageCss.length).to.equal(4, 'The index.astro page should generate 4 stylesheets, 1 for each <style> tag on the page.')
|
||||
});
|
||||
|
||||
it('resolves Styles from React', async () => {
|
||||
|
|
|
@ -24,14 +24,12 @@ describe('Doctype', () => {
|
|||
expect(html).to.match(/^<!DOCTYPE html>/i);
|
||||
});
|
||||
|
||||
// Note: parse5 converts this to <!DOCTYPE html> (HTML5). Uncomment if we want to support legacy doctypes.
|
||||
//
|
||||
// it('Preserves user provided doctype', async () => {
|
||||
// const html = await fixture.readFile('/preserve/index.html');
|
||||
it('Preserves user provided doctype', async () => {
|
||||
const html = await fixture.readFile('/preserve/index.html');
|
||||
|
||||
// // test that Doctype included was preserved
|
||||
// expect(html).to.match(new RegExp('^<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">', 'i'));
|
||||
// });
|
||||
// test that Doctype included was preserved
|
||||
expect(html).to.match(new RegExp('^<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">', 'i'));
|
||||
});
|
||||
|
||||
it('User provided doctype is case insensitive', async () => {
|
||||
const html = await fixture.readFile('/capital/index.html');
|
||||
|
|
|
@ -479,7 +479,6 @@ importers:
|
|||
'@types/send': ^0.17.1
|
||||
'@types/unist': ^2.0.6
|
||||
'@types/yargs-parser': ^21.0.0
|
||||
'@web/parse5-utils': ^1.3.0
|
||||
ast-types: ^0.14.2
|
||||
astro-scripts: workspace:*
|
||||
boxen: ^6.2.1
|
||||
|
@ -506,7 +505,6 @@ importers:
|
|||
mime: ^3.0.0
|
||||
mocha: ^9.2.2
|
||||
ora: ^6.1.0
|
||||
parse5: ^6.0.1
|
||||
path-browserify: ^1.0.1
|
||||
path-to-regexp: ^6.2.0
|
||||
postcss: ^8.4.12
|
||||
|
@ -546,7 +544,6 @@ importers:
|
|||
'@babel/traverse': 7.17.9
|
||||
'@proload/core': 0.2.2
|
||||
'@proload/plugin-tsm': 0.1.1_@proload+core@0.2.2
|
||||
'@web/parse5-utils': 1.3.0
|
||||
ast-types: 0.14.2
|
||||
boxen: 6.2.1
|
||||
ci-info: 3.3.0
|
||||
|
@ -569,7 +566,6 @@ importers:
|
|||
micromorph: 0.1.2
|
||||
mime: 3.0.0
|
||||
ora: 6.1.0
|
||||
parse5: 6.0.1
|
||||
path-browserify: 1.0.1
|
||||
path-to-regexp: 6.2.0
|
||||
postcss: 8.4.12
|
||||
|
@ -588,7 +584,6 @@ importers:
|
|||
sirv: 2.0.2
|
||||
slash: 4.0.0
|
||||
sourcemap-codec: 1.4.8
|
||||
srcset-parse: 1.1.0
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.0.1
|
||||
supports-esm: 1.0.0
|
||||
|
@ -623,6 +618,7 @@ importers:
|
|||
cheerio: 1.0.0-rc.10
|
||||
mocha: 9.2.2
|
||||
sass: 1.50.0
|
||||
srcset-parse: 1.1.0
|
||||
|
||||
packages/astro-prism:
|
||||
specifiers:
|
||||
|
@ -4488,14 +4484,6 @@ packages:
|
|||
/@vue/shared/3.2.31:
|
||||
resolution: {integrity: sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==}
|
||||
|
||||
/@web/parse5-utils/1.3.0:
|
||||
resolution: {integrity: sha512-Pgkx3ECc8EgXSlS5EyrgzSOoUbM6P8OKS471HLAyvOBcP1NCBn0to4RN/OaKASGq8qa3j+lPX9H14uA5AHEnQg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dependencies:
|
||||
'@types/parse5': 6.0.3
|
||||
parse5: 6.0.1
|
||||
dev: false
|
||||
|
||||
/@webcomponents/template-shadowroot/0.1.0:
|
||||
resolution: {integrity: sha512-ry84Vft6xtRBbd4M/ptRodbOLodV5AD15TYhyRghCRgIcJJKmYmJ2v2BaaWxygENwh6Uq3zTfGPmlckKT/GXsQ==}
|
||||
dev: false
|
||||
|
@ -9497,7 +9485,7 @@ packages:
|
|||
|
||||
/srcset-parse/1.1.0:
|
||||
resolution: {integrity: sha512-JWp4cG2eybkvKA1QUHGoNK6JDEYcOnSuhzNGjZuYUPqXreDl/VkkvP2sZW7Rmh+icuCttrR9ccb2WPIazyM/Cw==}
|
||||
dev: false
|
||||
dev: true
|
||||
|
||||
/statuses/2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
|
|
Loading…
Reference in a new issue