Skip to content

Commit

Permalink
[Content collections] Move generated types to .astro directory (#5786)
Browse files Browse the repository at this point in the history
* feat: change cacheDir to `.astro`

* feat: write reference in env.d.ts if none exists

* chore: update with-content types

* test: env.d.ts transform

* nit: setUp -> add

* refactor: content.d.ts -> types.d.ts

* chore: update confirmation log

* chore: changeset

* feat: inject env.d.ts if none exists

* feat: set up env.d.ts on `astro sync`

* chore: duplicate envTsPathRelative

* docs: update changeset

* fix: make srcDir if none exists

* fix: types.generated -> .astro in gitignore

* feat: add env.d.ts to test gitignore

* chore: remove env.d.ts from content-collections

* test: move sync tests to `astro sync`, add file write test

* refactor: simplify test gitignore to base

* fix: add / to `.astro` bc that scares me
  • Loading branch information
bholmesdev committed Jan 10, 2023
1 parent 813073a commit c218074
Show file tree
Hide file tree
Showing 21 changed files with 263 additions and 97 deletions.
9 changes: 9 additions & 0 deletions .changeset/curvy-foxes-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'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.

Astro will also generate the [TypeScript reference path](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-path-) to include `.astro` types in your project. This will update your project's `src/env.d.ts` file, or write one if none exists.
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
79 changes: 79 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,79 @@
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.mkdir(settings.config.srcDir, { recursive: true });
await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'), 'utf-8');
info(logging, 'astro', `Added ${bold(envTsPathRelativetoRoot)} types`);
}
}
Loading

0 comments on commit c218074

Please sign in to comment.