Skip to content

Commit

Permalink
fix(platform): cache content attributes if they have not changed (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturovt committed Jul 23, 2023
1 parent 7646549 commit e4387f2
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 7 deletions.
45 changes: 45 additions & 0 deletions packages/platform/src/lib/content-plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect } from 'vitest';
import * as fs from 'fs';

vi.mock('fs');

import { contentPlugin } from './content-plugin';

describe('content plugin', () => {
const [plugin] = contentPlugin();
const transform = (code: string, id: string): any => {
// Use `any` because not of the signatures are callable and it also expects
// to pass a valid `this` type.
const pluginTransform: any = plugin.transform;
return pluginTransform(code, id);
};

it('should skip transforming code if there is no `analog-content-list` at the end', async () => {
// Arrange
const code = 'Some_code';
const id = '/src/content/post.md';
// Act & Assert
expect(await transform(code, id)).toEqual(undefined);
});

it('should cache parsed attributes if the code is the same', async () => {
// Arrange
const code =
'---\n' +
'title: My First Post\n' +
'slug: 2022-12-27-my-first-post\n' +
'description: My First Post Description\n' +
'---\n' +
'\n' +
'Hello World\n';
const id = '/src/content/post.md?analog-content-list=true';
const readFileSyncSpy = vi.spyOn(fs, 'readFileSync').mockReturnValue(code);
const result =
'export default {"title":"My First Post","slug":"2022-12-27-my-first-post","description":"My First Post Description"}';
// Act & Assert
expect(await transform(code, id)).toEqual(result);
expect(await transform(code, id)).toEqual(result);
// Ensure the `readFileSync` has been called only once.
expect(readFileSyncSpy).toBeCalledTimes(1);
});
});
39 changes: 32 additions & 7 deletions packages/platform/src/lib/content-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
import { Plugin } from 'vite';
import * as fs from 'fs';

interface Content {
code: string;
attributes: string;
}

export function contentPlugin(): Plugin[] {
const cache = new Map<string, Content>();

return [
{
name: 'analogjs-content-frontmatter',
async transform(_code, id) {
async transform(code, id) {
// Transform only the frontmatter into a JSON object for lists
if (id.includes('.md?analog-content-list')) {
const fm: any = await import('front-matter');
const fileContents = fs.readFileSync(id.split('?')[0], 'utf8');
const frontmatter = fm(fileContents).attributes;
if (!id.includes('.md?analog-content-list')) {
return;
}

return `export default ${JSON.stringify(frontmatter)}`;
const cachedContent = cache.get(id);
// There's no reason to run `readFileSync` and frontmatter parsing if the
// `transform` hook is called with the same code. In such cases, we can simply
// return the cached attributes, which is faster than repeatedly reading files
// synchronously during the build process.
if (cachedContent?.code === code) {
return `export default ${cachedContent.attributes}`;
}

return;
const fm: any = await import('front-matter');
// The `default` property will be available in CommonJS environment, for instance,
// when running unit tests. It's safe to retrieve `default` first, since we still
// fallback to the original implementation.
const frontmatter = fm.default || fm;
const fileContents = fs.readFileSync(id.split('?')[0], 'utf8');
const { attributes } = frontmatter(fileContents);
const content = {
code,
attributes: JSON.stringify(attributes),
};
cache.set(id, content);

return `export default ${content.attributes}`;
},
},
];
Expand Down

0 comments on commit e4387f2

Please sign in to comment.