Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Content collections] Move generated types to .astro directory #5786

Merged
merged 19 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/curvy-foxes-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'astro': minor
---

Move generated content collection types to a `.astro` directory. This replaces the previously generated `src/content/types.generated.d.ts` file.

If you're using Git for version control, we recommend ignoring this generated directory by adding `.astro` to your .gitignore.

#### Migration

You will need a [TypeScript reference path](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-path-) to include `.astro` types in your project. Running `astro dev`, `astro build`, or `astro sync` will configure this automatically if your project has a `src/env.d.ts` file. Otherwise, you can add a `src/env.d.ts` file manually with the following contents:

```diff
/// <reference path="astro/client" />
+ /// <reference types="../.astro/types.d.ts" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean you don't get types without manually creating a file? Or was this already required? Is there anything we can do about that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be automatically generated if a src/env.d.ts is present! Otherwise, we assume you've deleted or moved this file to manage ambient types yourself. Since this file is home to the astro/client types, I'd assume most users have this file in their Astro project.

```
Original file line number Diff line number Diff line change
Expand Up @@ -55,44 +55,45 @@ declare module 'astro:content' {
};

const entryMap: {
blog: {
'first-post.md': {
id: 'first-post.md';
slug: 'first-post';
body: string;
collection: 'blog';
data: InferEntrySchema<'blog'>;
};
'markdown-style-guide.md': {
id: 'markdown-style-guide.md';
slug: 'markdown-style-guide';
body: string;
collection: 'blog';
data: InferEntrySchema<'blog'>;
};
'second-post.md': {
id: 'second-post.md';
slug: 'second-post';
body: string;
collection: 'blog';
data: InferEntrySchema<'blog'>;
};
'third-post.md': {
id: 'third-post.md';
slug: 'third-post';
body: string;
collection: 'blog';
data: InferEntrySchema<'blog'>;
};
'using-mdx.mdx': {
id: 'using-mdx.mdx';
slug: 'using-mdx';
body: string;
collection: 'blog';
data: InferEntrySchema<'blog'>;
};
};
"blog": {
"first-post.md": {
id: "first-post.md",
slug: "first-post",
body: string,
collection: "blog",
data: InferEntrySchema<"blog">
},
"markdown-style-guide.md": {
id: "markdown-style-guide.md",
slug: "markdown-style-guide",
body: string,
collection: "blog",
data: InferEntrySchema<"blog">
},
"second-post.md": {
id: "second-post.md",
slug: "second-post",
body: string,
collection: "blog",
data: InferEntrySchema<"blog">
},
"third-post.md": {
id: "third-post.md",
slug: "third-post",
body: string,
collection: "blog",
data: InferEntrySchema<"blog">
},
"using-mdx.mdx": {
id: "using-mdx.mdx",
slug: "using-mdx",
body: string,
collection: "blog",
data: InferEntrySchema<"blog">
},
},

};

type ContentConfig = typeof import('./config');
type ContentConfig = typeof import("../src/content/config");
}
1 change: 1 addition & 0 deletions examples/with-content/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
2 changes: 2 additions & 0 deletions packages/astro/src/cli/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { contentObservable, createContentTypesGenerator } from '../../content/in
import { getTimeStat } from '../../core/build/util.js';
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
import { info, LogOptions } from '../../core/logger/core.js';
import { setUpEnvTs } from '../../vite-plugin-inject-env-ts/index.js';

export async function sync(
settings: AstroSettings,
Expand All @@ -26,6 +27,7 @@ export async function sync(
}

info(logging, 'content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
await setUpEnvTs({ settings, logging, fs });

return 0;
}
4 changes: 1 addition & 3 deletions packages/astro/src/content/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@ export const VIRTUAL_MODULE_ID = 'astro:content';
export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@';
export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@';

export const CONTENT_BASE = 'types.generated';
export const CONTENT_FILE = CONTENT_BASE + '.mjs';
export const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts';
export const CONTENT_TYPES_FILE = 'types.d.ts';
2 changes: 1 addition & 1 deletion packages/astro/src/content/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { createContentTypesGenerator } from './types-generator.js';
export { contentObservable, getContentPaths } from './utils.js';
export { contentObservable, getContentPaths, getDotAstroTypeReference } from './utils.js';
export {
astroBundleDelayedAssetPlugin,
astroDelayedAssetPlugin,
Expand Down
13 changes: 8 additions & 5 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { info, LogOptions, warn } from '../core/logger/core.js';
import { appendForwardSlash, isRelativePath } from '../core/path.js';
import { getEnvTsPath } from '../vite-plugin-inject-env-ts/index.js';
import { contentFileExts, CONTENT_TYPES_FILE } from './consts.js';
import {
ContentConfig,
ContentObservable,
ContentPaths,
getContentPaths,
getDotAstroTypeReference,
getEntryInfo,
loadContentConfig,
NoCollectionError,
Expand Down Expand Up @@ -48,15 +50,12 @@ export async function createContentTypesGenerator({
settings,
}: CreateContentGeneratorParams): Promise<GenerateContentTypes> {
const contentTypes: ContentTypes = {};
const contentPaths: ContentPaths = getContentPaths({ srcDir: settings.config.srcDir });
const contentPaths = getContentPaths(settings.config);

let events: Promise<{ shouldGenerateTypes: boolean; error?: Error }>[] = [];
let debounceTimeout: NodeJS.Timeout | undefined;

const contentTypesBase = await fs.promises.readFile(
new URL(CONTENT_TYPES_FILE, contentPaths.generatedInputDir),
'utf-8'
);
const contentTypesBase = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8');

async function init() {
await handleEvent({ name: 'add', entry: contentPaths.config }, { logLevel: 'warn' });
Expand Down Expand Up @@ -306,6 +305,10 @@ async function writeContentFiles({
contentTypesStr += `},\n`;
}

if (!fs.existsSync(contentPaths.cacheDir)) {
fs.mkdirSync(contentPaths.cacheDir, { recursive: true });
}

let configPathRelativeToCacheDir = normalizePath(
path.relative(contentPaths.cacheDir.pathname, contentPaths.config.pathname)
);
Expand Down
29 changes: 22 additions & 7 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createServer, ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod';
import { AstroSettings } from '../@types/astro.js';
import { AstroConfig, AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { CONTENT_TYPES_FILE } from './consts.js';
import { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';

export const collectionConfigParser = z.object({
Expand All @@ -26,6 +27,15 @@ export const collectionConfigParser = z.object({
.optional(),
});

export function getDotAstroTypeReference({ root, srcDir }: { root: URL; srcDir: URL }) {
const { cacheDir } = getContentPaths({ root, srcDir });
const contentTypesRelativeToSrcDir = normalizePath(
path.relative(fileURLToPath(srcDir), fileURLToPath(new URL(CONTENT_TYPES_FILE, cacheDir)))
);

return `/// <reference path=${JSON.stringify(contentTypesRelativeToSrcDir)} />`;
}

export const contentConfigParser = z.object({
collections: z.record(collectionConfigParser),
});
Expand Down Expand Up @@ -201,7 +211,7 @@ export async function loadContentConfig({
fs: typeof fsMod;
settings: AstroSettings;
}): Promise<ContentConfig | Error> {
const contentPaths = getContentPaths({ srcDir: settings.config.srcDir });
const contentPaths = getContentPaths(settings.config);
const tempConfigServer: ViteDevServer = await createServer({
root: fileURLToPath(settings.config.root),
server: { middlewareMode: true, hmr: false },
Expand Down Expand Up @@ -267,16 +277,21 @@ export function contentObservable(initialCtx: ContentCtx): ContentObservable {
export type ContentPaths = {
contentDir: URL;
cacheDir: URL;
generatedInputDir: URL;
typesTemplate: URL;
virtualModTemplate: URL;
config: URL;
};

export function getContentPaths({ srcDir }: { srcDir: URL }): ContentPaths {
export function getContentPaths({
srcDir,
root,
}: Pick<AstroConfig, 'root' | 'srcDir'>): ContentPaths {
const templateDir = new URL('../../src/content/template/', import.meta.url);
return {
// Output generated types in content directory. May change in the future!
cacheDir: new URL('./content/', srcDir),
cacheDir: new URL('.astro/', root),
contentDir: new URL('./content/', srcDir),
generatedInputDir: new URL('../../src/content/template/', import.meta.url),
typesTemplate: new URL('types.d.ts', templateDir),
virtualModTemplate: new URL('virtual-mod.mjs', templateDir),
config: new URL('./content/config', srcDir),
};
}
3 changes: 1 addition & 2 deletions packages/astro/src/content/vite-plugin-content-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
import {
ContentConfig,
contentObservable,
ContentPaths,
getContentPaths,
getEntryData,
getEntryInfo,
Expand All @@ -36,7 +35,7 @@ export function astroContentServerPlugin({
logging,
mode,
}: AstroContentServerPluginParams): Plugin[] {
const contentPaths: ContentPaths = getContentPaths({ srcDir: settings.config.srcDir });
const contentPaths = getContentPaths(settings.config);
let contentDirExists = false;
let contentGenerator: GenerateContentTypes;
const contentConfigObserver = contentObservable({ status: 'loading' });
Expand Down
14 changes: 8 additions & 6 deletions packages/astro/src/content/vite-plugin-content-virtual-mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Plugin } from 'vite';
import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
import { contentFileExts, CONTENT_FILE, VIRTUAL_MODULE_ID } from './consts.js';
import { contentFileExts, VIRTUAL_MODULE_ID } from './consts.js';
import { getContentPaths } from './utils.js';

interface AstroContentVirtualModPluginParams {
Expand All @@ -14,15 +14,17 @@ interface AstroContentVirtualModPluginParams {
export function astroContentVirtualModPlugin({
settings,
}: AstroContentVirtualModPluginParams): Plugin {
const paths = getContentPaths({ srcDir: settings.config.srcDir });
const contentPaths = getContentPaths(settings.config);
const relContentDir = normalizePath(
appendForwardSlash(
prependForwardSlash(path.relative(settings.config.root.pathname, paths.contentDir.pathname))
prependForwardSlash(
path.relative(settings.config.root.pathname, contentPaths.contentDir.pathname)
)
)
);
const entryGlob = `${relContentDir}**/*{${contentFileExts.join(',')}}`;
const astroContentModContents = fsMod
.readFileSync(new URL(CONTENT_FILE, paths.generatedInputDir), 'utf-8')
const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace('@@CONTENT_DIR@@', relContentDir)
.replace('@@ENTRY_GLOB_PATH@@', entryGlob)
.replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob);
Expand All @@ -40,7 +42,7 @@ export function astroContentVirtualModPlugin({
load(id) {
if (id === astroContentVirtualModuleId) {
return {
code: astroContentModContents,
code: virtualModContents,
};
}
},
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import markdownVitePlugin from '../vite-plugin-markdown/index.js';
import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { astroInjectEnvTsPlugin } from '../vite-plugin-inject-env-ts/index.js';

interface CreateViteOptions {
settings: AstroSettings;
Expand Down Expand Up @@ -102,6 +103,7 @@ export async function createVite(
astroScriptsPageSSRPlugin({ settings }),
astroHeadPropagationPlugin({ settings }),
astroScannerPlugin({ settings, logging }),
astroInjectEnvTsPlugin({ settings, logging, fs }),
...(settings.config.experimental.contentCollections
? [
astroContentVirtualModPlugin({ settings }),
Expand Down
78 changes: 78 additions & 0 deletions packages/astro/src/vite-plugin-inject-env-ts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { AstroSettings } from '../@types/astro.js';
import type fsMod from 'node:fs';
import { normalizePath, Plugin } from 'vite';
import path from 'node:path';
import { getContentPaths, getDotAstroTypeReference } from '../content/index.js';
import { info, LogOptions } from '../core/logger/core.js';
import { fileURLToPath } from 'node:url';
import { bold } from 'kleur/colors';

export function getEnvTsPath({ srcDir }: { srcDir: URL }) {
return new URL('env.d.ts', srcDir);
}

export function astroInjectEnvTsPlugin({
settings,
logging,
fs,
}: {
settings: AstroSettings;
logging: LogOptions;
fs: typeof fsMod;
}): Plugin {
return {
name: 'astro-inject-env-ts',
// Use `post` to ensure project setup is complete
// Ex. `.astro` types have been written
enforce: 'post',
async config() {
await setUpEnvTs({ settings, logging, fs });
},
};
}

export async function setUpEnvTs({
settings,
logging,
fs,
}: {
settings: AstroSettings;
logging: LogOptions;
fs: typeof fsMod;
}) {
const envTsPath = getEnvTsPath(settings.config);
const dotAstroDir = getContentPaths(settings.config).cacheDir;
const dotAstroTypeReference = getDotAstroTypeReference(settings.config);
const envTsPathRelativetoRoot = normalizePath(
path.relative(fileURLToPath(settings.config.root), fileURLToPath(envTsPath))
);

if (fs.existsSync(envTsPath)) {
// Add `.astro` types reference if none exists
if (!fs.existsSync(dotAstroDir)) return;

let typesEnvContents = await fs.promises.readFile(envTsPath, 'utf-8');
const expectedTypeReference = getDotAstroTypeReference(settings.config);

if (!typesEnvContents.includes(expectedTypeReference)) {
typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
info(logging, 'content', `Added ${bold(envTsPathRelativetoRoot)} types`);
}
} else {
// Otherwise, inject the `env.d.ts` file
let referenceDefs: string[] = [];
if (settings.config.integrations.find((i) => i.name === '@astrojs/image')) {
referenceDefs.push('/// <reference types="@astrojs/image/client" />');
} else {
referenceDefs.push('/// <reference types="astro/client" />');
}

if (fs.existsSync(dotAstroDir)) {
referenceDefs.push(dotAstroTypeReference);
}

await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'));
info(logging, 'astro', `Added ${bold(envTsPathRelativetoRoot)} types`);
}
}
Loading