Prevent astro:content from depending on Node builtins (#6537)

* Prevent astro:content from depending on Node builtins

* Right file

* Move the plugin into test-plugins.js
This commit is contained in:
Matthew Phillips 2023-03-13 15:36:11 -04:00 committed by GitHub
parent 87d5e96da4
commit 6a7cf0712d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 37 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Prevent astro:content from depending on Node builtins

View file

@ -53,6 +53,8 @@
"./assets/services/sharp": "./dist/assets/services/sharp.js",
"./assets/services/squoosh": "./dist/assets/services/squoosh.js",
"./content/internal": "./dist/content/internal.js",
"./content/runtime": "./dist/content/runtime.js",
"./content/runtime-assets": "./dist/content/runtime-assets.js",
"./debug": "./components/Debug.astro",
"./internal/*": "./dist/runtime/server/*",
"./package.json": "./package.json",

View file

@ -0,0 +1,28 @@
import { z } from 'zod';
import { imageMetadata, type Metadata } from '../assets/utils/metadata.js';
export function createImage(options: { assetsDir: string; relAssetsDir: string }) {
return () => {
if (options.assetsDir === 'undefined') {
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
}
return z.string().transform(async (imagePath) => {
const fullPath = new URL(imagePath, options.assetsDir);
return await getImageMetadata(fullPath);
});
};
}
async function getImageMetadata(
imagePath: URL
): Promise<(Metadata & { __astro_asset: true }) | undefined> {
const meta = await imageMetadata(imagePath);
if (!meta) {
return undefined;
}
delete meta.orientation;
return { ...meta, __astro_asset: true };
}

View file

@ -1,5 +1,3 @@
import { z } from 'zod';
import { imageMetadata, type Metadata } from '../assets/utils/metadata.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { prependForwardSlash } from '../core/path.js';
@ -199,29 +197,3 @@ async function render({
remarkPluginFrontmatter: mod.frontmatter ?? {},
};
}
export function createImage(options: { assetsDir: string; relAssetsDir: string }) {
return () => {
if (options.assetsDir === 'undefined') {
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
}
return z.string().transform(async (imagePath) => {
const fullPath = new URL(imagePath, options.assetsDir);
return await getImageMetadata(fullPath);
});
};
}
async function getImageMetadata(
imagePath: URL
): Promise<(Metadata & { __astro_asset: true }) | undefined> {
const meta = await imageMetadata(imagePath);
if (!meta) {
return undefined;
}
delete meta.orientation;
return { ...meta, __astro_asset: true };
}

View file

@ -0,0 +1,9 @@
import {
createImage
} from 'astro/content/runtime-assets';
const assetsDir = '@@ASSETS_DIR@@';
export const image = createImage({
assetsDir,
});

View file

@ -3,8 +3,7 @@ import {
createCollectionToGlobResultMap,
createGetCollection,
createGetEntryBySlug,
createImage,
} from 'astro/content/internal';
} from 'astro/content/runtime';
export { z } from 'astro/zod';
@ -13,7 +12,6 @@ export function defineCollection(config) {
}
const contentDir = '@@CONTENT_DIR@@';
const assetsDir = '@@ASSETS_DIR@@';
const entryGlob = import.meta.glob('@@ENTRY_GLOB_PATH@@', {
query: { astroContent: true },
@ -40,7 +38,3 @@ export const getEntryBySlug = createGetEntryBySlug({
getCollection,
collectionToRenderEntryMap,
});
export const image = createImage({
assetsDir,
});

View file

@ -334,6 +334,7 @@ export type ContentPaths = {
cacheDir: URL;
typesTemplate: URL;
virtualModTemplate: URL;
virtualAssetsModTemplate: URL;
config: {
exists: boolean;
url: URL;
@ -352,6 +353,7 @@ export function getContentPaths(
assetsDir: new URL('./assets/', srcDir),
typesTemplate: new URL('types.d.ts', templateDir),
virtualModTemplate: new URL('virtual-mod.mjs', templateDir),
virtualAssetsModTemplate: new URL('virtual-mod-assets.mjs', templateDir),
config: configStats,
};
}

View file

@ -36,11 +36,16 @@ export function astroContentVirtualModPlugin({
const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace('@@CONTENT_DIR@@', relContentDir)
.replace('@@ASSETS_DIR@@', assetsDir)
.replace('@@ENTRY_GLOB_PATH@@', entryGlob)
.replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob);
const virtualAssetsModContents = fsMod
.readFileSync(contentPaths.virtualAssetsModTemplate, 'utf-8')
.replace('@@ASSETS_DIR@@', assetsDir);
const astroContentVirtualModuleId = '\0' + VIRTUAL_MODULE_ID;
const allContents = settings.config.experimental.assets ?
(virtualModContents + virtualAssetsModContents) :
virtualModContents;
return {
name: 'astro-content-virtual-mod-plugin',
@ -53,7 +58,7 @@ export function astroContentVirtualModPlugin({
load(id) {
if (id === astroContentVirtualModuleId) {
return {
code: virtualModContents,
code: allContents,
};
}
},

View file

@ -3,6 +3,7 @@ import * as cheerio from 'cheerio';
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
import testAdapter from './test-adapter.js';
import { preventNodeBuiltinDependencyPlugin } from './test-plugins.js';
describe('Content Collections', () => {
describe('Query', () => {
@ -222,6 +223,11 @@ describe('Content Collections', () => {
root: './fixtures/content-ssr-integration/',
output: 'server',
adapter: testAdapter(),
vite: {
plugins: [
preventNodeBuiltinDependencyPlugin()
]
}
});
await fixture.build();
app = await fixture.loadTestAdapterApp();

View file

@ -0,0 +1,17 @@
export function preventNodeBuiltinDependencyPlugin() {
// Verifies that `astro:content` does not have a hard dependency on Node builtins.
// This is to verify it will run on Cloudflare and Deno
return {
name: 'verify-no-node-stuff',
generateBundle() {
const nodeModules = ['node:fs', 'node:url', 'node:worker_threads', 'node:path'];
nodeModules.forEach(name => {
const mod = this.getModuleInfo(name);
if(mod) {
throw new Error(`Node builtins snuck in: ${name}`)
}
});
}
};
}