Use new compiler resolvePath option (#5133)

This commit is contained in:
Bjorn Lu 2022-10-21 14:08:07 +08:00 committed by GitHub
parent 643535dcd4
commit 1c477dd8d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 57 additions and 311 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix `.css?raw` usage

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Update `@astrojs/compiler` and use the new `resolvePath` option. This allows removing much of the runtime code, which should improve rendering performance for Astro and MDX pages.

View file

@ -2,3 +2,4 @@
import Fake from '../abc.astro';
---
<h1>Testing unresolved frontmatter import</h1>
<Fake />

View file

@ -98,7 +98,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
"@astrojs/compiler": "^0.27.1",
"@astrojs/compiler": "^0.28.0",
"@astrojs/language-server": "^0.26.2",
"@astrojs/markdown-remark": "^1.1.3",
"@astrojs/telemetry": "^1.0.1",

View file

@ -16,7 +16,7 @@ import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config';
import type { AstroCookies } from '../core/cookies';
import type { AstroComponentFactory, Metadata } from '../runtime/server';
import type { AstroComponentFactory } from '../runtime/server';
export type {
MarkdownHeading,
MarkdownMetadata,
@ -968,7 +968,6 @@ export type AsyncRendererComponentFn<U> = (
/** Generic interface for a component (Astro, Svelte, React, etc.) */
export interface ComponentInstance {
$$metadata: Metadata;
default: AstroComponentFactory;
css?: string[];
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;

View file

@ -223,38 +223,6 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
});
output.source = minifiedCSS;
}
} else if (output.type === 'chunk') {
// vite:css-post removes "pure CSS" JavaScript chunks, that is chunks that only contain a comment
// about it being a CSS module. We need to keep these chunks around because Astro
// re-imports all modules as their namespace `import * as module1 from 'some/path';
// in order to determine if one of them is a side-effectual web component.
// If we ever get rid of that feature, the code below can be removed.
for (const [imp, bindings] of Object.entries(output.importedBindings)) {
if (imp.startsWith('chunks/') && !bundle[imp] && output.code.includes(imp)) {
// This just creates an empty chunk module so that the main entry module
// that is importing it doesn't break.
const depChunk: OutputChunk = {
type: 'chunk',
fileName: imp,
name: imp,
facadeModuleId: imp,
code: `/* Pure CSS chunk ${imp} */ ${bindings
.map((b) => `export const ${b} = {};`)
.join('')}`,
dynamicImports: [],
implicitlyLoadedBefore: [],
importedBindings: {},
imports: [],
referencedFiles: [],
exports: Array.from(bindings),
isDynamicEntry: false,
isEntry: false,
isImplicitEntry: false,
modules: {},
};
bundle[imp] = depChunk;
}
}
}
}
}

View file

@ -1,12 +1,11 @@
import type { TransformResult } from '@astrojs/compiler';
import path from 'path';
import type { AstroConfig } from '../../@types/astro';
import type { TransformStyle } from './types';
import { transform } from '@astrojs/compiler';
import { AstroErrorCodes } from '../errors.js';
import { prependForwardSlash, removeLeadingForwardSlashWindows } from '../path.js';
import { AggregateError, resolveJsToTs, viteID } from '../util.js';
import { prependForwardSlash } from '../path.js';
import { AggregateError, resolvePath, viteID } from '../util.js';
import { createStylePreprocessor } from './style.js';
type CompilationCache = Map<string, CompileResult>;
@ -37,13 +36,7 @@ async function compile({
// use `sourcemap: "both"` so that sourcemap is included in the code
// result passed to esbuild, but also available in the catch handler.
const transformResult = await transform(source, {
// For Windows compat, prepend filename with `/`.
// Note this is required because the compiler uses URLs to parse and built paths.
// TODO: Ideally the compiler could expose a `resolvePath` function so that we can
// unify how we handle path in a bundler-agnostic way.
// At the meantime workaround with a slash and remove them in `astro:postprocess`
// when they are used in `client:component-path`.
pathname: prependForwardSlash(filename),
pathname: filename,
projectRoot: config.root.toString(),
site: config.site?.toString(),
sourcefile: filename,
@ -54,6 +47,9 @@ async function compile({
// TODO: baseline flag
experimentalStaticExtraction: true,
preprocessStyle: createStylePreprocessor(transformStyle, cssDeps, cssTransformErrors),
async resolvePath(specifier) {
return resolvePath(specifier, filename);
},
})
.catch((err) => {
// throw compiler errors here if encountered
@ -88,32 +84,6 @@ async function compile({
},
});
// Also fix path before returning. Example original resolvedPaths:
// - @astrojs/preact/client.js
// - @/components/Foo.vue
// - /Users/macos/project/src/Foo.vue
// - /C:/Windows/project/src/Foo.vue
for (const c of compileResult.clientOnlyComponents) {
c.resolvedPath = removeLeadingForwardSlashWindows(c.resolvedPath);
// The compiler trims .jsx by default, prevent this
if (c.specifier.endsWith('.jsx') && !c.resolvedPath.endsWith('.jsx')) {
c.resolvedPath += '.jsx';
}
if (path.isAbsolute(c.resolvedPath)) {
c.resolvedPath = resolveJsToTs(c.resolvedPath);
}
}
for (const c of compileResult.hydratedComponents) {
c.resolvedPath = removeLeadingForwardSlashWindows(c.resolvedPath);
// The compiler trims .jsx by default, prevent this
if (c.specifier.endsWith('.jsx') && !c.resolvedPath.endsWith('.jsx')) {
c.resolvedPath += '.jsx';
}
if (path.isAbsolute(c.resolvedPath)) {
c.resolvedPath = resolveJsToTs(c.resolvedPath);
}
}
return compileResult;
}

View file

@ -14,7 +14,6 @@ import { isPage, resolveIdToUrl } from '../../util.js';
import { createRenderContext, renderPage as coreRenderPage } from '../index.js';
import { filterFoundRenderers, loadRenderer } from '../renderer.js';
import { RouteCache } from '../route-cache.js';
import { collectMdMetadata } from '../util.js';
import { getStylesForURL } from './css.js';
import type { DevelopmentEnvironment } from './environment';
import { getScriptsForURL } from './scripts.js';
@ -94,16 +93,6 @@ export async function preload({
const renderers = await loadRenderers(env.viteServer, env.settings);
// Load the module from the Vite SSR Runtime.
const mod = (await env.viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
if (env.viteServer.config.mode === 'development' || !mod?.$$metadata) {
return [renderers, mod];
}
// append all nested markdown metadata to mod.$$metadata
const modGraph = await env.viteServer.moduleGraph.getModuleByUrl(fileURLToPath(filePath));
if (modGraph) {
await collectMdMetadata(mod.$$metadata, modGraph, env.viteServer);
}
return [renderers, mod];
}

View file

@ -1,15 +1,3 @@
import type { ModuleNode, ViteDevServer } from 'vite';
import type { Metadata } from '../../runtime/server/metadata.js';
/** Check if a URL is already valid */
export function isValidURL(url: string): boolean {
try {
new URL(url);
return true;
} catch (e) {}
return false;
}
// https://vitejs.dev/guide/features.html#css-pre-processors
export const STYLE_EXTENSIONS = new Set([
'.css',
@ -22,59 +10,9 @@ export const STYLE_EXTENSIONS = new Set([
'.less',
]);
// duplicate const from vite-plugin-markdown
// can't import directly due to Deno bundling issue
// (node fs import failing during prod builds)
const MARKDOWN_IMPORT_FLAG = '?mdImport';
const cssRe = new RegExp(
`\\.(${Array.from(STYLE_EXTENSIONS)
.map((s) => s.slice(1))
.join('|')})($|\\?)`
);
export const isCSSRequest = (request: string): boolean => cssRe.test(request);
// During prod builds, some modules have dependencies we should preload by hand
// Ex. markdown files imported asynchronously or via Astro.glob(...)
// This calls each md file's $$loadMetadata to discover those dependencies
// and writes all results to the input `metadata` object
const seenMdMetadata = new Set<string>();
export async function collectMdMetadata(
metadata: Metadata,
modGraph: ModuleNode,
viteServer: ViteDevServer
) {
const importedModules = [...(modGraph?.importedModules ?? [])];
await Promise.all(
importedModules.map(async (importedModule) => {
// recursively check for importedModules
if (!importedModule.id || seenMdMetadata.has(importedModule.id)) return;
seenMdMetadata.add(importedModule.id);
await collectMdMetadata(metadata, importedModule, viteServer);
if (!importedModule?.id?.endsWith(MARKDOWN_IMPORT_FLAG)) return;
const mdSSRMod = await viteServer.ssrLoadModule(importedModule.id);
const mdMetadata = (await mdSSRMod.$$loadMetadata?.()) as Metadata;
if (!mdMetadata) return;
for (let mdMod of mdMetadata.modules) {
mdMod.specifier = mdMetadata.resolvePath(mdMod.specifier);
metadata.modules.push(mdMod);
}
for (let mdHoisted of mdMetadata.hoisted) {
metadata.hoisted.push(mdHoisted);
}
for (let mdHydrated of mdMetadata.hydratedComponents) {
metadata.hydratedComponents.push(mdHydrated);
}
for (let mdClientOnly of mdMetadata.clientOnlyComponents) {
metadata.clientOnlyComponents.push(mdClientOnly);
}
for (let mdHydrationDirective of mdMetadata.hydrationDirectives) {
metadata.hydrationDirectives.add(mdHydrationDirective);
}
})
);
}

View file

@ -4,7 +4,7 @@ import path from 'path';
import resolve from 'resolve';
import slash from 'slash';
import { fileURLToPath, pathToFileURL } from 'url';
import type { ErrorPayload, ViteDevServer } from 'vite';
import { ErrorPayload, normalizePath, ViteDevServer } from 'vite';
import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro';
import { prependForwardSlash, removeTrailingForwardSlash } from './path.js';
@ -227,6 +227,18 @@ export function resolveJsToTs(filePath: string) {
return filePath;
}
/**
* Resolve the hydration paths so that it can be imported in the client
*/
export function resolvePath(specifier: string, importer: string) {
if (specifier.startsWith('.')) {
const absoluteSpecifier = path.resolve(path.dirname(importer), specifier);
return resolveJsToTs(normalizePath(absoluteSpecifier));
} else {
return specifier;
}
}
export const AggregateError =
typeof (globalThis as any).AggregateError !== 'undefined'
? (globalThis as any).AggregateError

View file

@ -1,8 +1,6 @@
import type { PluginObj } from '@babel/core';
import * as t from '@babel/types';
import npath from 'path';
import { normalizePath } from 'vite';
import { resolveJsToTs } from '../core/util.js';
import { resolvePath } from '../core/util.js';
import { HydrationDirectiveProps } from '../runtime/server/hydration.js';
import type { PluginMetadata } from '../vite-plugin-astro/types';
@ -217,13 +215,7 @@ export default function astroJSX(): PluginObj {
const meta = path.getData('import');
if (meta) {
let resolvedPath: string;
if (meta.path.startsWith('.')) {
resolvedPath = normalizePath(npath.resolve(npath.dirname(state.filename!), meta.path));
resolvedPath = resolveJsToTs(resolvedPath);
} else {
resolvedPath = meta.path;
}
const resolvedPath = resolvePath(meta.path, state.filename!);
if (isClientOnly) {
(state.file.metadata as PluginMetadata).astro.clientOnlyComponents.push({
@ -297,13 +289,7 @@ export default function astroJSX(): PluginObj {
}
}
}
let resolvedPath: string;
if (meta.path.startsWith('.')) {
resolvedPath = normalizePath(npath.resolve(npath.dirname(state.filename!), meta.path));
resolvedPath = resolveJsToTs(resolvedPath);
} else {
resolvedPath = meta.path;
}
const resolvedPath = resolvePath(meta.path, state.filename!);
if (isClientOnly) {
(state.file.metadata as PluginMetadata).astro.clientOnlyComponents.push({
exportName: meta.name,

View file

@ -2,8 +2,6 @@ export { createAstro } from './astro-global.js';
export { renderEndpoint } from './endpoint.js';
export { escapeHTML, HTMLBytes, HTMLString, markHTMLString, unescapeHTML } from './escape.js';
export { renderJSX } from './jsx.js';
export type { Metadata } from './metadata';
export { createMetadata } from './metadata.js';
export {
addAttribute,
defineScriptVars,

View file

@ -1,100 +0,0 @@
import { removeLeadingForwardSlashWindows } from '../../core/path.js';
interface ModuleInfo {
module: Record<string, any>;
specifier: string;
}
interface ComponentMetadata {
componentExport: string;
componentUrl: string;
}
interface CreateMetadataOptions {
modules: ModuleInfo[];
hydratedComponents: any[];
clientOnlyComponents: any[];
hydrationDirectives: Set<string>;
hoisted: any[];
}
export class Metadata {
public filePath: string;
public modules: ModuleInfo[];
public hoisted: any[];
public hydratedComponents: any[];
public clientOnlyComponents: any[];
public hydrationDirectives: Set<string>;
private mockURL: URL;
private metadataCache: Map<any, ComponentMetadata | null>;
constructor(filePathname: string, opts: CreateMetadataOptions) {
this.modules = opts.modules;
this.hoisted = opts.hoisted;
this.hydratedComponents = opts.hydratedComponents;
this.clientOnlyComponents = opts.clientOnlyComponents;
this.hydrationDirectives = opts.hydrationDirectives;
this.filePath = removeLeadingForwardSlashWindows(filePathname);
this.mockURL = new URL(filePathname, 'http://example.com');
this.metadataCache = new Map<any, ComponentMetadata | null>();
}
resolvePath(specifier: string): string {
if (specifier.startsWith('.')) {
// NOTE: ideally we should use `path.resolve` here, but this is part
// of server output code, which needs to work on platform that doesn't
// have the `path` module. Use `URL` here since we deal with posix only.
const url = new URL(specifier, this.mockURL);
return removeLeadingForwardSlashWindows(decodeURI(url.pathname));
} else {
return specifier;
}
}
getPath(Component: any): string | null {
const metadata = this.getComponentMetadata(Component);
return metadata?.componentUrl || null;
}
getExport(Component: any): string | null {
const metadata = this.getComponentMetadata(Component);
return metadata?.componentExport || null;
}
private getComponentMetadata(Component: any): ComponentMetadata | null {
if (this.metadataCache.has(Component)) {
return this.metadataCache.get(Component)!;
}
const metadata = this.findComponentMetadata(Component);
this.metadataCache.set(Component, metadata);
return metadata;
}
private findComponentMetadata(Component: any): ComponentMetadata | null {
const isCustomElement = typeof Component === 'string';
for (const { module, specifier } of this.modules) {
const id = this.resolvePath(specifier);
for (const [key, value] of Object.entries(module)) {
if (isCustomElement) {
if (key === 'tagName' && Component === value) {
return {
componentExport: key,
componentUrl: id,
};
}
} else if (Component === value) {
return {
componentExport: key,
componentUrl: id,
};
}
}
}
return null;
}
}
export function createMetadata(filePathname: string, options: CreateMetadataOptions) {
return new Metadata(filePathname, options);
}

View file

@ -1,28 +1,17 @@
import { parse as babelParser } from '@babel/parser';
import type {
ArrowFunctionExpressionKind,
CallExpressionKind,
StringLiteralKind,
} from 'ast-types/gen/kinds';
import type { ArrowFunctionExpressionKind, CallExpressionKind } from 'ast-types/gen/kinds';
import type { NodePath } from 'ast-types/lib/node-path';
import npath from 'path';
import { parse, print, types, visit } from 'recast';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro';
import { removeLeadingForwardSlashWindows } from '../core/path.js';
import { resolveJsToTs } from '../core/util.js';
// Check for `Astro.glob()`. Be very forgiving of whitespace. False positives are okay.
const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/;
const CLIENT_COMPONENT_PATH_REGEX = /['"]client:component-path['"]:/;
interface AstroPluginOptions {
settings: AstroSettings;
}
// esbuild transforms the component-scoped Astro into Astro2, so need to check both.
const validAstroGlobalNames = new Set(['Astro', 'Astro2']);
export default function astro(_opts: AstroPluginOptions): Plugin {
return {
name: 'astro:postprocess',
@ -34,7 +23,7 @@ export default function astro(_opts: AstroPluginOptions): Plugin {
// Optimization: Detect usage with a quick string match.
// Only perform the transform if this function is found
if (!ASTRO_GLOB_REGEX.test(code) && !CLIENT_COMPONENT_PATH_REGEX.test(code)) {
if (!ASTRO_GLOB_REGEX.test(code)) {
return null;
}
@ -85,33 +74,6 @@ export default function astro(_opts: AstroPluginOptions): Plugin {
);
return false;
},
visitObjectProperty: function (path) {
// Filter out none 'client:component-path' properties
if (
!types.namedTypes.StringLiteral.check(path.node.key) ||
path.node.key.value !== 'client:component-path' ||
!types.namedTypes.StringLiteral.check(path.node.value)
) {
this.traverse(path);
return;
}
// Patch up client:component-path value that has leading slash on Windows.
// See `compile.ts` for more details, this will be fixed in the Astro compiler.
const valuePath = path.get('value') as NodePath;
let value = valuePath.value.value;
value = removeLeadingForwardSlashWindows(value);
// Add back `.jsx` stripped by the compiler by loosely checking the code
if (code.includes(npath.basename(value) + '.jsx')) {
value += '.jsx';
}
value = resolveJsToTs(value);
valuePath.replace({
type: 'StringLiteral',
value,
} as StringLiteralKind);
return false;
},
});
const result = print(ast);

View file

@ -120,9 +120,6 @@ export default function markdown({ settings }: AstroPluginOptions): Plugin {
export async function compiledContent() {
return load().then((m) => m.compiledContent());
}
export function $$loadMetadata() {
return load().then((m) => m.$$metadata);
}
// Deferred
export default async function load() {

View file

@ -266,6 +266,13 @@ describe('CSS', function () {
);
});
});
describe('Vite features', () => {
it('.css?raw return a string', () => {
const el = $('#css-raw');
expect(el.text()).to.equal('.foo {color: red;}');
});
});
});
// with "build" handling CSS checking, the dev tests are mostly testing the paths resolve in dev
@ -375,5 +382,10 @@ describe('CSS', function () {
'Should not have found a preload for the dynamic CSS'
);
});
it('.css?raw return a string', () => {
const el = $('#css-raw');
expect(el.text()).to.equal('.foo {color: red;}');
});
});
});

View file

@ -23,6 +23,7 @@ import SvelteDynamic from '../components/SvelteDynamic.svelte';
import '../styles/imported-url.css';
import '../styles/imported.sass';
import '../styles/imported.scss';
import raw from '../styles/raw.css?raw'
---
<html>
@ -71,6 +72,7 @@ import '../styles/imported.scss';
<VueScss />
<ReactDynamic client:load />
<SvelteDynamic client:load />
<pre id="css-raw">{raw}</pre>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
.foo {color: red;}

View file

@ -12,5 +12,6 @@ import something from '../something.js';
</head>
<body>
<h1>Astro</h1>
<p>{something}</p>
</body>
</html>

View file

@ -362,7 +362,7 @@ importers:
packages/astro:
specifiers:
'@astrojs/compiler': ^0.27.1
'@astrojs/compiler': ^0.28.0
'@astrojs/language-server': ^0.26.2
'@astrojs/markdown-remark': ^1.1.3
'@astrojs/telemetry': ^1.0.1
@ -458,7 +458,7 @@ importers:
yargs-parser: ^21.0.1
zod: ^3.17.3
dependencies:
'@astrojs/compiler': 0.27.2
'@astrojs/compiler': 0.28.0
'@astrojs/language-server': 0.26.2
'@astrojs/markdown-remark': link:../markdown/remark
'@astrojs/telemetry': link:../telemetry
@ -3793,8 +3793,8 @@ packages:
resolution: {integrity: sha512-vBMPy9ok4iLapSyCCT1qsZ9dK7LkVFl9mObtLEmWiec9myGHS9h2kQY2xzPeFNJiWXUf9O6tSyQpQTy5As/p3g==}
dev: false
/@astrojs/compiler/0.27.2:
resolution: {integrity: sha512-VG4X87cUkcmT40HqEwShQzUgl0VSnVTszobbmnhAOkHzdoWMxhwAm61A2o5fEsv6eEK8M0lW/fGwkSofYM5GcQ==}
/@astrojs/compiler/0.28.0:
resolution: {integrity: sha512-Ju8GAbupzSG5/binq+4Em7sb1yoaWhirIe0cdzeb+BDOfPssu6BWZxY+qytZC6zV+lQlZpujcanMsnexyj1C3A==}
dev: false
/@astrojs/language-server/0.26.2: