mirror of
https://github.com/withastro/astro.git
synced 2024-12-23 21:53:55 -05:00
Improve third-party Astro package support (#4623)
This commit is contained in:
parent
965a493a39
commit
eb1862b4e6
7 changed files with 267 additions and 24 deletions
5
.changeset/fifty-avocados-lay.md
Normal file
5
.changeset/fifty-avocados-lay.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"astro": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve third-party Astro package support
|
|
@ -2,6 +2,8 @@ import type { AstroConfig } from '../@types/astro';
|
||||||
import type { LogOptions } from './logger/core';
|
import type { LogOptions } from './logger/core';
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import * as vite from 'vite';
|
import * as vite from 'vite';
|
||||||
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
||||||
|
@ -174,34 +176,115 @@ function sortPlugins(pluginOptions: vite.PluginOption[]) {
|
||||||
// Scans `projectRoot` for third-party Astro packages that could export an `.astro` file
|
// 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`
|
// `.astro` files need to be built by Vite, so these should use `noExternal`
|
||||||
async function getAstroPackages({ root }: AstroConfig): Promise<string[]> {
|
async function getAstroPackages({ root }: AstroConfig): Promise<string[]> {
|
||||||
const pkgUrl = new URL('./package.json', root);
|
const { astroPackages } = new DependencyWalker(root);
|
||||||
const pkgPath = fileURLToPath(pkgUrl);
|
return astroPackages;
|
||||||
if (!fs.existsSync(pkgPath)) return [];
|
}
|
||||||
|
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
/**
|
||||||
|
* Recursively walk a project’s 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 aren’t
|
||||||
|
* themselves also Astro packages.
|
||||||
|
*/
|
||||||
|
class DependencyWalker {
|
||||||
|
private readonly require: NodeRequire;
|
||||||
|
private readonly astroDeps = new Set<string>();
|
||||||
|
private readonly nonAstroDeps = new Set<string>();
|
||||||
|
|
||||||
const deps = [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})];
|
constructor(root: URL) {
|
||||||
|
const pkgUrl = new URL('./package.json', root);
|
||||||
|
this.require = createRequire(pkgUrl);
|
||||||
|
const pkgPath = fileURLToPath(pkgUrl);
|
||||||
|
if (!fs.existsSync(pkgPath)) return;
|
||||||
|
|
||||||
return deps.filter((dep) => {
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||||
// Attempt: package is common and not Astro. ❌ Skip these for perf
|
const deps = [
|
||||||
if (isCommonNotAstro(dep)) return false;
|
...Object.keys(pkg.dependencies || {}),
|
||||||
// Attempt: package is named `astro-something`. ✅ Likely a community package
|
...Object.keys(pkg.devDependencies || {}),
|
||||||
if (/^astro\-/.test(dep)) return true;
|
];
|
||||||
const depPkgUrl = new URL(`./node_modules/${dep}/package.json`, root);
|
|
||||||
const depPkgPath = fileURLToPath(depPkgUrl);
|
|
||||||
if (!fs.existsSync(depPkgPath)) return false;
|
|
||||||
|
|
||||||
const {
|
this.scanDependencies(deps);
|
||||||
dependencies = {},
|
}
|
||||||
peerDependencies = {},
|
|
||||||
keywords = [],
|
/** The dependencies we determined were likely to include `.astro` files. */
|
||||||
} = JSON.parse(fs.readFileSync(depPkgPath, 'utf-8'));
|
public get astroPackages(): string[] {
|
||||||
// Attempt: package relies on `astro`. ✅ Definitely an Astro package
|
return Array.from(this.astroDeps);
|
||||||
if (peerDependencies.astro || dependencies.astro) return true;
|
}
|
||||||
// Attempt: package is tagged with `astro` or `astro-component`. ✅ Likely a community package
|
|
||||||
if (keywords.includes('astro') || keywords.includes('astro-component')) return true;
|
private seen(dep: string): boolean {
|
||||||
return false;
|
return this.astroDeps.has(dep) || this.nonAstroDeps.has(dep);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/** Try to load a directory’s `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 dependency’s `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 doesn’t 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 (e) {
|
||||||
|
// 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 haven’t 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 = [
|
const COMMON_DEPENDENCIES_NOT_ASTRO = [
|
||||||
|
@ -256,3 +339,12 @@ function isCommonNotAstro(dep: string): boolean {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PkgJSON {
|
||||||
|
name: string;
|
||||||
|
dependencies?: Record<string, string>;
|
||||||
|
devDependencies?: Record<string, string>;
|
||||||
|
peerDependencies?: Record<string, string>;
|
||||||
|
keywords?: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
4
packages/astro/test/fixtures/third-party-astro/astro.config.mjs
vendored
Normal file
4
packages/astro/test/fixtures/third-party-astro/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({});
|
9
packages/astro/test/fixtures/third-party-astro/package.json
vendored
Normal file
9
packages/astro/test/fixtures/third-party-astro/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "@e2e/third-party-astro",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"astro": "workspace:*",
|
||||||
|
"astro-embed": "^0.1.1"
|
||||||
|
}
|
||||||
|
}
|
16
packages/astro/test/fixtures/third-party-astro/src/pages/astro-embed.astro
vendored
Normal file
16
packages/astro/test/fixtures/third-party-astro/src/pages/astro-embed.astro
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
import { YouTube } from 'astro-embed'
|
||||||
|
---
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Third-Party Package Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Third-Party .astro test</h1>
|
||||||
|
<YouTube id="https://youtu.be/xtTy5nKay_Y" />
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
43
packages/astro/test/third-party-astro.test.js
Normal file
43
packages/astro/test/third-party-astro.test.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
|
describe('third-party .astro component', () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/third-party-astro/',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('build', () => {
|
||||||
|
before(async () => {
|
||||||
|
await fixture.build();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a page using a third-party .astro component', async () => {
|
||||||
|
const html = await fixture.readFile('/astro-embed/index.html');
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('Third-Party .astro test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dev', () => {
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await devServer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a page using a third-party .astro component', async () => {
|
||||||
|
const html = await fixture.fetch('/astro-embed/').then((res) => res.text());
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
expect($('h1').text()).to.equal('Third-Party .astro test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -972,6 +972,14 @@ importers:
|
||||||
'@astrojs/tailwind': link:../../../../integrations/tailwind
|
'@astrojs/tailwind': link:../../../../integrations/tailwind
|
||||||
astro: link:../../..
|
astro: link:../../..
|
||||||
|
|
||||||
|
packages/astro/e2e/fixtures/third-party-astro:
|
||||||
|
specifiers:
|
||||||
|
astro: workspace:*
|
||||||
|
astro-embed: ^0.1.1
|
||||||
|
dependencies:
|
||||||
|
astro: link:../../..
|
||||||
|
astro-embed: 0.1.1_astro@packages+astro
|
||||||
|
|
||||||
packages/astro/e2e/fixtures/ts-resolution:
|
packages/astro/e2e/fixtures/ts-resolution:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@astrojs/react': workspace:*
|
'@astrojs/react': workspace:*
|
||||||
|
@ -2029,6 +2037,14 @@ importers:
|
||||||
postcss: 8.4.16
|
postcss: 8.4.16
|
||||||
tailwindcss: 3.1.8_postcss@8.4.16
|
tailwindcss: 3.1.8_postcss@8.4.16
|
||||||
|
|
||||||
|
packages/astro/test/fixtures/third-party-astro:
|
||||||
|
specifiers:
|
||||||
|
astro: workspace:*
|
||||||
|
astro-embed: ^0.1.1
|
||||||
|
dependencies:
|
||||||
|
astro: link:../../..
|
||||||
|
astro-embed: 0.1.1_astro@packages+astro
|
||||||
|
|
||||||
packages/astro/test/fixtures/type-imports:
|
packages/astro/test/fixtures/type-imports:
|
||||||
specifiers:
|
specifiers:
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
|
@ -3174,6 +3190,39 @@ packages:
|
||||||
leven: 3.1.0
|
leven: 3.1.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@astro-community/astro-embed-integration/0.1.0_astro@packages+astro:
|
||||||
|
resolution: {integrity: sha512-qR4us0hAqIYo6MduvpXLrjeakX04afDILa7WkQbmWj3c4sbOqIcFCirLrmFs+dPjcPkv2Zpl2l3PxN3G6+ONSA==}
|
||||||
|
peerDependencies:
|
||||||
|
astro: ^1.0.0-beta.10
|
||||||
|
dependencies:
|
||||||
|
'@astro-community/astro-embed-twitter': 0.1.3_astro@packages+astro
|
||||||
|
'@astro-community/astro-embed-youtube': 0.1.1_astro@packages+astro
|
||||||
|
astro: link:packages/astro
|
||||||
|
unist-util-select: 4.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@astro-community/astro-embed-twitter/0.1.3_astro@packages+astro:
|
||||||
|
resolution: {integrity: sha512-lcOBnzhczNrngkafzD+8BGKiK8oJvahg3/QUuWgueNwHRU8C+18brdxKc1i4ttZWgAt1A5u+jx21Tc4bquMUzg==}
|
||||||
|
peerDependencies:
|
||||||
|
astro: ^1.0.0-beta.10
|
||||||
|
dependencies:
|
||||||
|
'@astro-community/astro-embed-utils': 0.0.3
|
||||||
|
astro: link:packages/astro
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@astro-community/astro-embed-utils/0.0.3:
|
||||||
|
resolution: {integrity: sha512-hXwSMtSAL3V9fnFHps+/CoDIJst26U/qSdI7srIQ8GPmFqdbcqJd/qOqYzGezAR/qTM8gmTjDCGOuVI0Z+xT3Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@astro-community/astro-embed-youtube/0.1.1_astro@packages+astro:
|
||||||
|
resolution: {integrity: sha512-qIf5nr3BMB/pWJWf7x/t86CIjpPA69eVKQql7TNJW7lTYL2SVPFA9WowsfvvrhNN9aWV/kTaSpW9e/m4FtXdkQ==}
|
||||||
|
peerDependencies:
|
||||||
|
astro: ^1.0.0-beta.10
|
||||||
|
dependencies:
|
||||||
|
astro: link:packages/astro
|
||||||
|
lite-youtube-embed: 0.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@astrojs/compiler/0.19.0:
|
/@astrojs/compiler/0.19.0:
|
||||||
resolution: {integrity: sha512-8nvyxZTfCXLyRmYfTttpJT6EPhfBRg0/q4J/Jj3/pNPLzp+vs05ZdktsY6QxAREaOMAnNEtSqcrB4S5DsXOfRg==}
|
resolution: {integrity: sha512-8nvyxZTfCXLyRmYfTttpJT6EPhfBRg0/q4J/Jj3/pNPLzp+vs05ZdktsY6QxAREaOMAnNEtSqcrB4S5DsXOfRg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -9727,6 +9776,17 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/astro-embed/0.1.1_astro@packages+astro:
|
||||||
|
resolution: {integrity: sha512-NBnLDB0PygbahCBFeGDPzmW4/PJSrieWgjN7mN8vmStUACM+cdTz1vhLDSWt4LlbWxozq0x9G1dTnoVbHyYKLA==}
|
||||||
|
peerDependencies:
|
||||||
|
astro: ^1.0.0-beta.10
|
||||||
|
dependencies:
|
||||||
|
'@astro-community/astro-embed-integration': 0.1.0_astro@packages+astro
|
||||||
|
'@astro-community/astro-embed-twitter': 0.1.3_astro@packages+astro
|
||||||
|
'@astro-community/astro-embed-youtube': 0.1.1_astro@packages+astro
|
||||||
|
astro: link:packages/astro
|
||||||
|
dev: false
|
||||||
|
|
||||||
/async/3.2.4:
|
/async/3.2.4:
|
||||||
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
|
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -13056,6 +13116,10 @@ packages:
|
||||||
lit-html: 2.3.1
|
lit-html: 2.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/lite-youtube-embed/0.2.0:
|
||||||
|
resolution: {integrity: sha512-XXXAk5sbvtjjwbie3XG+6HppgTm1HTGL/Uk9z9NkJH53o7puZLur434heHzAjkS60hZB3vT4ls25zl5rMiX4EA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/load-yaml-file/0.2.0:
|
/load-yaml-file/0.2.0:
|
||||||
resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==}
|
resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
@ -17016,6 +17080,16 @@ packages:
|
||||||
unist-util-visit: 4.1.1
|
unist-util-visit: 4.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/unist-util-select/4.0.1:
|
||||||
|
resolution: {integrity: sha512-zPozyEo5vr1csbHf1TqlQrnuLVJ0tNMo63og3HrnINh2+OIDAgQpqHVr+0BMw1DIVHJV8ft/e6BZqtvD1Y5enw==}
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 2.0.6
|
||||||
|
css-selector-parser: 1.4.1
|
||||||
|
nth-check: 2.1.1
|
||||||
|
unist-util-is: 5.1.1
|
||||||
|
zwitch: 2.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/unist-util-stringify-position/3.0.2:
|
/unist-util-stringify-position/3.0.2:
|
||||||
resolution: {integrity: sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==}
|
resolution: {integrity: sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue