Skip to content

Commit

Permalink
Merge branch 'main' into feat/redirect-default-language
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico authored Jan 17, 2024
2 parents e159b10 + bc2edd4 commit 598a1ae
Show file tree
Hide file tree
Showing 46 changed files with 705 additions and 360 deletions.
6 changes: 6 additions & 0 deletions .changeset/calm-socks-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@astrojs/markdown-remark": minor
"astro": minor
---

Allows remark plugins to pass options specifying how images in `.md` files will be optimized
16 changes: 16 additions & 0 deletions .changeset/itchy-clouds-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"astro": minor
---

Removes the requirement for non-content files and assets inside content collections to be prefixed with an underscore. For files with extensions like `.astro` or `.css`, you can now remove underscores without seeing a warning in the terminal.

```diff
src/content/blog/
post.mdx
- _styles.css
- _Component.astro
+ styles.css
+ Component.astro
```

Continue to use underscores in your content collections to exclude individual content files, such as drafts, from the build output.
5 changes: 5 additions & 0 deletions .changeset/polite-dogs-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/markdoc": patch
---

Removes unnecessary `shikiji` dependency
8 changes: 8 additions & 0 deletions .changeset/six-scissors-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@astrojs/markdown-remark": minor
"astro": minor
---

Adds a new `markdown.shikiConfig.transformers` config option. You can use this option to transform the Shikiji hast (AST format of the generated HTML) to customize the final HTML. Also updates Shikiji to the latest stable version.

See [Shikiji's documentation](https://shikiji.netlify.app/guide/transformers) for more details about creating your own custom transformers, and [a list of common transformers](https://shikiji.netlify.app/packages/transformers) you can add directly to your project.
24 changes: 24 additions & 0 deletions .changeset/sixty-dogs-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"astro": minor
---

Adds an experimental flag `clientPrerender` to prerender your prefetched pages on the client with the [Speculation Rules API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API).

```js
// astro.config.mjs
{
prefetch: {
prefetchAll: true,
defaultStrategy: 'viewport',
},
experimental: {
clientPrerender: true,
},
}
```

Enabling this feature overrides the default `prefetch` behavior globally to prerender links on the client according to your `prefetch` configuration. Instead of appending a `<link>` tag to the head of the document or fetching the page with JavaScript, a `<script>` tag will be appended with the corresponding speculation rules.

Client side prerendering requires browser support. If the Speculation Rules API is not supported, `prefetch` will fallback to the supported strategy.

See the [Prefetch Guide](https://docs.astro.build/en/guides/prefetch/) for more `prefetch` options and usage.
103 changes: 103 additions & 0 deletions packages/astro/e2e/prefetch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,106 @@ test.describe("Prefetch (prefetchAll: true, defaultStrategy: 'load')", () => {
expect(page.locator('link[rel="prefetch"][href$="/prefetch-load"]')).toBeDefined();
});
});

// Playwrights `request` event does not appear to fire when using the speculation rules API
// Instead of checking for the added url, each test checks to see if `document.head`
// contains a `script[type=speculationrules]` that has the `url` in it.
test.describe('Prefetch (default), Experimental ({ clientPrerender: true })', () => {
/**
* @param {import('@playwright/test').Page} page
* @param {string} url
* @returns the number of script[type=speculationrules] that have the url
*/
async function scriptIsInHead(page, url) {
return await page.evaluate((testUrl) => {
const scripts = document.head.querySelectorAll('script[type="speculationrules"]');
let count = 0;
for (const script of scripts) {
/** @type {{ prerender: { urls: string[] }[] }} */
const speculationRules = JSON.parse(script.textContent);
const specUrl = speculationRules.prerender.at(0).urls.at(0);
const indexOf = specUrl.indexOf(testUrl);
if (indexOf > -1) count++;
}
return count;
}, url);
}

let devServer;

test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer({
experimental: {
clientPrerender: true,
},
});
});

test.afterAll(async () => {
await devServer.stop();
});

test('Link without data-astro-prefetch should not prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-default')).toBeFalsy();
});

test('data-astro-prefetch="false" should not prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-false')).toBeFalsy();
});

test('Link with search param should prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '?search-param=true')).toBeFalsy();
await page.locator('#prefetch-search-param').hover();
await page.waitForFunction(
() => document.querySelectorAll('script[type=speculationrules]').length === 2
);
expect(await scriptIsInHead(page, '?search-param=true')).toBeTruthy();
});

test('data-astro-prefetch="tap" should prefetch on tap', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-tap')).toBeFalsy();
await page.locator('#prefetch-tap').dragTo(page.locator('#prefetch-hover'));
expect(await scriptIsInHead(page, '/prefetch-tap')).toBeTruthy();
});

test('data-astro-prefetch="hover" should prefetch on hover', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-hover')).toBeFalsy();
await page.locator('#prefetch-hover').hover();
await page.waitForFunction(
() => document.querySelectorAll('script[type=speculationrules]').length === 2
);
expect(await scriptIsInHead(page, '/prefetch-hover')).toBeTruthy();
});

test('data-astro-prefetch="viewport" should prefetch on viewport', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-viewport')).toBeFalsy();
// Scroll down to show the element
await page.locator('#prefetch-viewport').scrollIntoViewIfNeeded();
await page.waitForFunction(
() => document.querySelectorAll('script[type=speculationrules]').length === 2
);
expect(await scriptIsInHead(page, '/prefetch-viewport')).toBeTruthy();
});

test('manual prefetch() works once', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(0);
await page.locator('#prefetch-manual').click();
expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(1);

// prefetch again should have no effect
await page.locator('#prefetch-manual').click();
expect(await scriptIsInHead(page, '/prefetch-manual')).toEqual(1);
});

test('data-astro-prefetch="load" should prefetch', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
expect(await scriptIsInHead(page, 'prefetch-load')).toBeTruthy();
});
});
3 changes: 2 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"resolve": "^1.22.4",
"semver": "^7.5.4",
"server-destroy": "^1.0.1",
"shikiji": "^0.6.13",
"shikiji": "^0.9.18",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0",
"tsconfck": "^3.0.0",
Expand Down Expand Up @@ -224,6 +224,7 @@
"remark-code-titles": "^0.1.2",
"rollup": "^4.5.0",
"sass": "^1.69.5",
"shikiji-core": "^0.9.18",
"srcset-parse": "^1.1.0",
"unified": "^11.0.4"
},
Expand Down
36 changes: 36 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1595,6 +1595,42 @@ export interface AstroUserConfig {
* ```
*/
contentCollectionCache?: boolean;

/**
* @docs
* @name experimental.clientPrerender
* @type {boolean}
* @default `false`
* @version: 4.2.0
* @description
* Enables pre-rendering your prefetched pages on the client in supported browsers.
*
* This feature uses the experimental [Speculation Rules Web API](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) and overrides the default `prefetch` behavior globally to prerender links on the client.
* You may wish to review the [possible risks when prerendering on the client](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API#unsafe_prefetching) before enabling this feature.
*
* Enable client side prerendering in your `astro.config.mjs` along with any desired `prefetch` configuration options:
*
* ```js
* // astro.config.mjs
* {
* prefetch: {
* prefetchAll: true,
* defaultStrategy: 'viewport',
* },
* experimental: {
* clientPrerender: true,
* },
* }
* ```
*
* Continue to use the `data-astro-prefetch` attribute on any `<a />` link on your site to opt in to prefetching.
* Instead of appending a `<link>` tag to the head of the document or fetching the page with JavaScript, a `<script>` tag will be appended with the corresponding speculation rules.
*
* Client side prerendering requires browser support. If the Speculation Rules API is not supported, `prefetch` will fallback to the supported strategy.
*
* See the [Prefetch Guide](https://docs.astro.build/en/guides/prefetch/) for more `prefetch` options and usage.
*/
clientPrerender?: boolean;
};
}

Expand Down
31 changes: 1 addition & 30 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ type CreateContentGeneratorParams = {
fs: typeof fsMod;
};

class UnsupportedFileTypeError extends Error {}

export async function createContentTypesGenerator({
contentConfigObserver,
fs,
Expand Down Expand Up @@ -109,9 +107,7 @@ export async function createContentTypesGenerator({
return { typesGenerated: true };
}

async function handleEvent(
event: ContentEvent
): Promise<{ shouldGenerateTypes: boolean; error?: Error }> {
async function handleEvent(event: ContentEvent): Promise<{ shouldGenerateTypes: boolean }> {
if (event.name === 'addDir' || event.name === 'unlinkDir') {
const collection = normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
Expand Down Expand Up @@ -147,21 +143,6 @@ export async function createContentTypesGenerator({
await reloadContentConfigObserver({ fs, settings, viteServer });
return { shouldGenerateTypes: true };
}
if (fileType === 'unsupported') {
// Avoid warning if file was deleted.
if (event.name === 'unlink') {
return { shouldGenerateTypes: false };
}
const { id } = getContentEntryIdAndSlug({
entry: event.entry,
contentDir: contentPaths.contentDir,
collection: '',
});
return {
shouldGenerateTypes: false,
error: new UnsupportedFileTypeError(id),
};
}

const { entry } = event;
const { contentDir } = contentPaths;
Expand Down Expand Up @@ -319,16 +300,6 @@ export async function createContentTypesGenerator({
}

events = [];
for (const response of eventResponses) {
if (response.error instanceof UnsupportedFileTypeError) {
logger.warn(
'content',
`Unsupported file type ${bold(
response.error.message
)} found. Prefix filename with an underscore (\`_\`) to ignore.`
);
}
}
const observable = contentConfigObserver.get();
if (eventResponses.some((r) => r.shouldGenerateTypes)) {
await writeContentFiles({
Expand Down
25 changes: 4 additions & 21 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import type {
AstroSettings,
ContentEntryType,
DataEntryType,
ImageInputFormat,
} from '../@types/astro.js';
import { VALID_INPUT_FORMATS } from '../assets/consts.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';

import { formatYAMLException, isYAMLException } from '../core/errors/utils.js';
Expand Down Expand Up @@ -247,15 +245,11 @@ export function getEntryType(
paths: Pick<ContentPaths, 'config' | 'contentDir'>,
contentFileExts: string[],
dataFileExts: string[]
): 'content' | 'data' | 'config' | 'ignored' | 'unsupported' {
const { ext, base } = path.parse(entryPath);
): 'content' | 'data' | 'config' | 'ignored' {
const { ext } = path.parse(entryPath);
const fileUrl = pathToFileURL(entryPath);

if (
hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir) ||
isOnIgnoreList(base) ||
isImageAsset(ext)
) {
if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir)) {
return 'ignored';
} else if (contentFileExts.includes(ext)) {
return 'content';
Expand All @@ -264,21 +258,10 @@ export function getEntryType(
} else if (fileUrl.href === paths.config.url.href) {
return 'config';
} else {
return 'unsupported';
return 'ignored';
}
}

function isOnIgnoreList(fileName: string) {
return ['.DS_Store'].includes(fileName);
}

/**
* Return if a file extension is a valid image asset, so we can avoid outputting a warning for them.
*/
function isImageAsset(fileExt: string) {
return VALID_INPUT_FORMATS.includes(fileExt.slice(1) as ImageInputFormat);
}

export function hasUnderscoreBelowContentDirectoryPath(
fileUrl: URL,
contentDir: ContentPaths['contentDir']
Expand Down
Loading

0 comments on commit 598a1ae

Please sign in to comment.