Improve Astro libraries config handling (#5203)

This commit is contained in:
Bjorn Lu 2022-10-27 14:54:41 +08:00 committed by GitHub
parent 22ca6a6056
commit 3d99fdd1e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 120 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Improve Astro libraries config handling

View file

@ -158,6 +158,7 @@
"unist-util-visit": "^4.1.0",
"vfile": "^5.3.2",
"vite": "~3.1.3",
"vitefu": "^0.1.0",
"yargs-parser": "^21.0.1",
"zod": "^3.17.3"
},

View file

@ -1,11 +1,9 @@
import type { AstroSettings } from '../@types/astro';
import type { LogOptions } from './logger/core';
import fs from 'fs';
import { createRequire } from 'module';
import path from 'path';
import { fileURLToPath } from 'url';
import * as vite from 'vite';
import { crawlFrameworkPkgs } from 'vitefu';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import astroViteServerPlugin from '../vite-plugin-astro-server/index.js';
import astroVitePlugin from '../vite-plugin-astro/index.js';
@ -58,7 +56,31 @@ export async function createVite(
commandConfig: vite.InlineConfig,
{ settings, logging, mode }: CreateViteOptions
): Promise<vite.InlineConfig> {
const thirdPartyAstroPackages = await getAstroPackages(settings);
const astroPkgsConfig = await crawlFrameworkPkgs({
root: fileURLToPath(settings.config.root),
isBuild: mode === 'build',
isFrameworkPkgByJson(pkgJson) {
return (
// Attempt: package relies on `astro`. ✅ Definitely an Astro package
pkgJson.peerDependencies?.astro ||
pkgJson.dependencies?.astro ||
// Attempt: package is tagged with `astro` or `astro-component`. ✅ Likely a community package
pkgJson.keywords?.includes('astro') ||
pkgJson.keywords?.includes('astro-component') ||
// Attempt: package is named `astro-something` or `@scope/astro-something`. ✅ Likely a community package
/^(@[^\/]+\/)?astro\-/.test(pkgJson.name)
);
},
isFrameworkPkgByName(pkgName) {
const isNotAstroPkg = isCommonNotAstro(pkgName);
if (isNotAstroPkg) {
return false;
} else {
return undefined;
}
},
});
// Start with the Vite configuration that Astro core needs
const commonConfig: vite.InlineConfig = {
cacheDir: fileURLToPath(new URL('./node_modules/.vite/', settings.config.root)), // using local caches allows Astro to be used in monorepos, etc.
@ -126,11 +148,14 @@ export async function createVite(
conditions: ['astro'],
},
ssr: {
noExternal: [...getSsrNoExternalDeps(settings.config.root), ...thirdPartyAstroPackages],
noExternal: [
...getSsrNoExternalDeps(settings.config.root),
...astroPkgsConfig.ssr.noExternal,
],
// shiki is imported by Code.astro, which is no-externalized (processed by Vite).
// However, shiki's deps are in CJS and trips up Vite's dev SSR transform, externalize
// shiki to load it with node instead.
external: mode === 'dev' ? ['shiki'] : [],
external: [...(mode === 'dev' ? ['shiki'] : []), ...astroPkgsConfig.ssr.external],
},
};
@ -174,120 +199,6 @@ function sortPlugins(pluginOptions: vite.PluginOption[]) {
pluginOptions.splice(jsxPluginIndex, 0, mdxPlugin);
}
// Scans `projectRoot` for third-party Astro packages that could export an `.astro` file
// `.astro` files need to be built by Vite, so these should use `noExternal`
async function getAstroPackages(settings: AstroSettings): Promise<string[]> {
const { astroPackages } = new DependencyWalker(settings.config.root);
return astroPackages;
}
/**
* Recursively walk a projects dependency tree trying to find Astro packages.
* - If the current node is an Astro package, we continue walking its child dependencies.
* - If the current node is not an Astro package, we bail out of walking that branch.
* This assumes it is unlikely for Astro packages to be dependencies of packages that arent
* themselves also Astro packages.
*/
class DependencyWalker {
private readonly require: NodeRequire;
private readonly astroDeps = new Set<string>();
private readonly nonAstroDeps = new Set<string>();
constructor(root: URL) {
const pkgUrl = new URL('./package.json', root);
this.require = createRequire(pkgUrl);
const pkgPath = fileURLToPath(pkgUrl);
if (!fs.existsSync(pkgPath)) return;
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const deps = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.devDependencies || {}),
];
this.scanDependencies(deps);
}
/** The dependencies we determined were likely to include `.astro` files. */
public get astroPackages(): string[] {
return Array.from(this.astroDeps);
}
private seen(dep: string): boolean {
return this.astroDeps.has(dep) || this.nonAstroDeps.has(dep);
}
/** Try to load a directorys `package.json` file from the filesystem. */
private readPkgJSON(dir: string): PkgJSON | void {
try {
const filePath = path.join(dir, 'package.json');
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
} catch (e) {}
}
/** Try to resolve a dependencys `package.json` even if not a package export. */
private resolvePkgJSON(dep: string): PkgJSON | void {
try {
const pkgJson: PkgJSON = this.require(dep + '/package.json');
return pkgJson;
} catch (e) {
// Most likely error is that the dependency doesnt include `package.json` in its package `exports`.
try {
// Walk up from default export until we find `package.json` with name === dep.
let dir = path.dirname(this.require.resolve(dep));
while (dir) {
const pkgJSON = this.readPkgJSON(dir);
if (pkgJSON && pkgJSON.name === dep) return pkgJSON;
const parentDir = path.dirname(dir);
if (parentDir === dir) break;
dir = parentDir;
}
} catch {
// Give up! Who knows where the `package.json` is…
}
}
}
private scanDependencies(deps: string[]): void {
const newDeps: string[] = [];
for (const dep of deps) {
// Attempt: package is common and not Astro. ❌ Skip these for perf
if (isCommonNotAstro(dep)) {
this.nonAstroDeps.add(dep);
continue;
}
const pkgJson = this.resolvePkgJSON(dep);
if (!pkgJson) {
this.nonAstroDeps.add(dep);
continue;
}
const { dependencies = {}, peerDependencies = {}, keywords = [] } = pkgJson;
if (
// Attempt: package relies on `astro`. ✅ Definitely an Astro package
peerDependencies.astro ||
dependencies.astro ||
// Attempt: package is tagged with `astro` or `astro-component`. ✅ Likely a community package
keywords.includes('astro') ||
keywords.includes('astro-component') ||
// Attempt: package is named `astro-something` or `@scope/astro-something`. ✅ Likely a community package
/^(@[^\/]+\/)?astro\-/.test(dep)
) {
this.astroDeps.add(dep);
// Collect any dependencies of this Astro package we havent seen yet.
const unknownDependencies = Object.keys(dependencies).filter((d) => !this.seen(d));
newDeps.push(...unknownDependencies);
} else {
this.nonAstroDeps.add(dep);
}
}
if (newDeps.length) this.scanDependencies(newDeps);
}
}
const COMMON_DEPENDENCIES_NOT_ASTRO = [
'autoprefixer',
'react',

View file

@ -455,6 +455,7 @@ importers:
unist-util-visit: ^4.1.0
vfile: ^5.3.2
vite: ~3.1.3
vitefu: ^0.1.0
yargs-parser: ^21.0.1
zod: ^3.17.3
dependencies:
@ -517,6 +518,7 @@ importers:
unist-util-visit: 4.1.1
vfile: 5.3.5
vite: 3.1.8_sass@1.55.0
vitefu: 0.1.0_vite@3.1.8
yargs-parser: 21.1.1
zod: 3.19.1
devDependencies:
@ -18014,6 +18016,18 @@ packages:
fsevents: 2.3.2
dev: false
/vitefu/0.1.0_vite@3.1.8:
resolution: {integrity: sha512-5MQSHP9yr0HIve8q4XNb7QXfO1P4tzZDZP99qH0FM5ClcwYddeGXRDQ4TQYRUeXLjZ+vLecirHtGNpwFFUF7sw==}
peerDependencies:
vite: ^3.0.0
peerDependenciesMeta:
vite:
optional: true
dependencies:
import-meta-resolve: 2.1.0
vite: 3.1.8_sass@1.55.0
dev: false
/vitest/0.20.3:
resolution: {integrity: sha512-cXMjTbZxBBUUuIF3PUzEGPLJWtIMeURBDXVxckSHpk7xss4JxkiiWh5cnIlfGyfJne2Ii3QpbiRuFL5dMJtljw==}
engines: {node: '>=v14.16.0'}