summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Drew Powers <1369770+drwpow@users.noreply.github.com> 2021-05-11 17:31:52 -0600
committerGravatar GitHub <noreply@github.com> 2021-05-11 17:31:52 -0600
commit7184149514260c9c5f8525648af1c0f2e51c998e (patch)
tree251fad305a2e22ea91adecd1e1f751b67c2bab09
parentd2eb413a6e4d423880bb3af93b63e3f37a6f1049 (diff)
downloadastro-7184149514260c9c5f8525648af1c0f2e51c998e.tar.gz
astro-7184149514260c9c5f8525648af1c0f2e51c998e.tar.zst
astro-7184149514260c9c5f8525648af1c0f2e51c998e.zip
Add Astro.request.canonicalURL and Astro.site to global (#199)
-rw-r--r--.changeset/cold-windows-exercise.md5
-rw-r--r--docs/api.md22
-rw-r--r--examples/blog/public/global.scss (renamed from examples/blog/public/global.css)0
-rw-r--r--examples/blog/src/components/MainHead.astro7
-rw-r--r--examples/blog/src/pages/index.astro6
-rw-r--r--packages/astro/src/build.ts7
-rw-r--r--packages/astro/src/build/rss.ts4
-rw-r--r--packages/astro/src/build/sitemap.ts8
-rw-r--r--packages/astro/src/build/util.ts10
-rw-r--r--packages/astro/src/cli.ts2
-rw-r--r--packages/astro/src/compiler/codegen/index.ts62
-rw-r--r--packages/astro/src/compiler/index.ts4
-rw-r--r--packages/astro/src/config.ts26
-rw-r--r--packages/astro/src/dev.ts6
-rw-r--r--packages/astro/src/logger.ts4
-rw-r--r--packages/astro/src/runtime.ts17
-rw-r--r--packages/astro/test/astro-global.test.js45
-rw-r--r--packages/astro/test/astro-request.test.js19
-rw-r--r--packages/astro/test/fixtures/astro-global/astro.config.mjs6
-rw-r--r--packages/astro/test/fixtures/astro-global/src/layouts/post.astro12
-rw-r--r--packages/astro/test/fixtures/astro-global/src/pages/$posts.astro28
-rw-r--r--packages/astro/test/fixtures/astro-global/src/pages/index.astro10
-rw-r--r--packages/astro/test/fixtures/astro-global/src/pages/post/post-2.md6
-rw-r--r--packages/astro/test/fixtures/astro-global/src/pages/post/post.md6
-rw-r--r--packages/astro/test/fixtures/astro-request/src/pages/index.astro10
25 files changed, 234 insertions, 98 deletions
diff --git a/.changeset/cold-windows-exercise.md b/.changeset/cold-windows-exercise.md
new file mode 100644
index 000000000..d0e5b37bf
--- /dev/null
+++ b/.changeset/cold-windows-exercise.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Added canonical URL and site globals for .astro files
diff --git a/docs/api.md b/docs/api.md
index e45c87bd8..9037b75de 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -4,14 +4,6 @@
The `Astro` global is available in all contexts in `.astro` files. It has the following functions:
-#### `config`
-
-`Astro.config` returns an object with the following properties:
-
-| Name | Type | Description |
-| :----- | :------- | :--------------------------------------------------------------------------------------------------------- |
-| `site` | `string` | Your website’s public root domain. Set it with `site: "https://mysite.com"` in your [Astro config][config] |
-
#### `fetchContent()`
`Astro.fetchContent()` is a way to load local `*.md` files into your static site setup. You can either use this on its own, or within [Astro Collections][docs-collections].
@@ -47,9 +39,16 @@ const data = Astro.fetchContent('../pages/post/*.md'); // returns an array of po
`Astro.request` returns an object with the following properties:
-| Name | Type | Description |
-| :---- | :---- | :------------------------------------- |
-| `url` | `URL` | The URL of the request being rendered. |
+| Name | Type | Description |
+| :------------- | :---- | :---------------------------------------------- |
+| `url` | `URL` | The URL of the request being rendered. |
+| `canonicalURL` | `URL` | [Canonical URL][canonical] of the current page. |
+
+⚠️ Temporary restriction: this is only accessible in top-level pages and not in sub-components.
+
+#### `site`
+
+`Astro.site` returns a `URL` made from `buildOptions.site` in your Astro config. If undefined, this will return a URL generated from `localhost`.
### `collection`
@@ -147,6 +146,7 @@ Astro will generate an RSS 2.0 feed at `/feed/[collection].xml` (for example, `/
<link rel="alternate" type="application/rss+xml" title="My RSS Feed" href="/feed/podcast.xml" />
```
+[canonical]: https://en.wikipedia.org/wiki/Canonical_link_element
[config]: ../README.md#%EF%B8%8F-configuration
[docs-collections]: ./collections.md
[rss]: #-rss-feed
diff --git a/examples/blog/public/global.css b/examples/blog/public/global.scss
index a6007631a..a6007631a 100644
--- a/examples/blog/public/global.css
+++ b/examples/blog/public/global.scss
diff --git a/examples/blog/src/components/MainHead.astro b/examples/blog/src/components/MainHead.astro
index bff812b0c..dfeb9dfb4 100644
--- a/examples/blog/src/components/MainHead.astro
+++ b/examples/blog/src/components/MainHead.astro
@@ -4,6 +4,9 @@ export let title: string;
export let description: string;
export let image: string | undefined;
export let type: string | undefined;
+export let next: string | undefined;
+export let prev: string | undefined;
+export let canonicalURL: string | undefined;
// internal data
const OG_TYPES = {
@@ -17,6 +20,10 @@ const OG_TYPES = {
<title>{title}</title>
<meta name="description" content={description} />
<link rel="stylesheet" href="/global.css" />
+<link rel="sitemap" href="/sitemap.xml" />
+<link rel="canonical" href={canonicalURL} />
+{next && <link rel="next" href={next} />}
+{prev && <link rel="prev" href={prev} />}
<!-- OpenGraph -->
<meta property="og:title" content={title} />
diff --git a/examples/blog/src/pages/index.astro b/examples/blog/src/pages/index.astro
index 2af3a1a03..19ea4ebf9 100644
--- a/examples/blog/src/pages/index.astro
+++ b/examples/blog/src/pages/index.astro
@@ -21,7 +21,11 @@ let firstThree = allPosts.slice(0, 3);
<html>
<head>
<title>{title}</title>
- <MainHead title={title} description={description} />
+ <MainHead
+ title={title}
+ description={description}
+ canonicalURL={Astro.request.canonicalURL.href}
+ />
</head>
<body>
diff --git a/packages/astro/src/build.ts b/packages/astro/src/build.ts
index 262b4525b..439d4ac9b 100644
--- a/packages/astro/src/build.ts
+++ b/packages/astro/src/build.ts
@@ -17,7 +17,7 @@ import { buildCollectionPage, buildStaticPage, getPageType } from './build/page.
import { generateSitemap } from './build/sitemap.js';
import { logURLStats, collectBundleStats, mapBundleStatsToURLStats } from './build/stats.js';
import { getDistPath, sortSet, stopTimer } from './build/util.js';
-import { debug, defaultLogDestination, error, info, trapWarn } from './logger.js';
+import { debug, defaultLogDestination, error, info, warn, trapWarn } from './logger.js';
import { createRuntime } from './runtime.js';
const logging: LogOptions = {
@@ -55,6 +55,9 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
dest: defaultLogDestination,
};
+ // warn users if missing config item in build that may result in broken SEO (can’t disable, as they should provide this)
+ warn(logging, 'config', `Set "buildOptions.site" to generate correct canonical URLs and sitemap`);
+
const mode: RuntimeMode = 'production';
const runtime = await createRuntime(astroConfig, { mode, logging: runtimeLogging });
const { runtimeConfig } = runtime;
@@ -170,8 +173,6 @@ export async function build(astroConfig: AstroConfig): Promise<0 | 1> {
await fs.promises.writeFile(sitemapPath, sitemap, 'utf8');
info(logging, 'build', green('✔'), 'sitemap built.');
debug(logging, 'build', `built sitemap [${stopTimer(timer.sitemap)}]`);
- } else if (astroConfig.buildOptions.sitemap) {
- info(logging, 'tip', `Set "buildOptions.site" in astro.config.mjs to generate a sitemap.xml, or set "buildOptions.sitemap: false" to disable this message.`);
}
// write to disk and free up memory
diff --git a/packages/astro/src/build/rss.ts b/packages/astro/src/build/rss.ts
index b75ed908b..e7a12da55 100644
--- a/packages/astro/src/build/rss.ts
+++ b/packages/astro/src/build/rss.ts
@@ -27,7 +27,7 @@ export function generateRSS<T>(input: { data: T[]; site: string } & CollectionRS
// title, description, customData
xml += `<title><![CDATA[${input.title}]]></title>`;
xml += `<description><![CDATA[${input.description}]]></description>`;
- xml += `<link>${canonicalURL('/feed/' + filename + '.xml', input.site)}</link>`;
+ xml += `<link>${canonicalURL('/feed/' + filename + '.xml', input.site).href}</link>`;
if (typeof input.customData === 'string') xml += input.customData;
// items
@@ -40,7 +40,7 @@ export function generateRSS<T>(input: { data: T[]; site: string } & CollectionRS
if (!result.title) throw new Error(`[${filename}] rss.item() returned object but required "title" is missing.`);
if (!result.link) throw new Error(`[${filename}] rss.item() returned object but required "link" is missing.`);
xml += `<title><![CDATA[${result.title}]]></title>`;
- xml += `<link>${canonicalURL(result.link, input.site)}</link>`;
+ xml += `<link>${canonicalURL(result.link, input.site).href}</link>`;
if (result.description) xml += `<description><![CDATA[${result.description}]]></description>`;
if (result.pubDate) {
// note: this should be a Date, but if user provided a string or number, we can work with that, too.
diff --git a/packages/astro/src/build/sitemap.ts b/packages/astro/src/build/sitemap.ts
index 5095019c7..7d6bf62a8 100644
--- a/packages/astro/src/build/sitemap.ts
+++ b/packages/astro/src/build/sitemap.ts
@@ -4,18 +4,18 @@ import { canonicalURL } from './util';
/** Construct sitemap.xml given a set of URLs */
export function generateSitemap(buildState: BuildOutput, site: string): string {
- const pages: string[] = [];
+ const uniqueURLs = new Set<string>();
// TODO: find way to respect <link rel="canonical"> URLs here
// TODO: find way to exclude pages from sitemap
// look through built pages, only add HTML
for (const id of Object.keys(buildState)) {
- if (buildState[id].contentType !== 'text/html' || id.endsWith('/1/index.html')) continue; // note: exclude auto-generated "page 1" pages (duplicates of index)
- let url = canonicalURL(id.replace(/index\.html$/, ''), site);
- pages.push(url);
+ if (buildState[id].contentType !== 'text/html') continue;
+ uniqueURLs.add(canonicalURL(id, site).href);
}
+ const pages = [...uniqueURLs];
pages.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })); // sort alphabetically so sitemap is same each time
let sitemap = `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;
diff --git a/packages/astro/src/build/util.ts b/packages/astro/src/build/util.ts
index afd9f5b32..6b8834f43 100644
--- a/packages/astro/src/build/util.ts
+++ b/packages/astro/src/build/util.ts
@@ -5,11 +5,11 @@ import path from 'path';
import { fileURLToPath, URL } from 'url';
/** Normalize URL to its canonical form */
-export function canonicalURL(url: string, base?: string): string {
- return new URL(
- path.extname(url) ? url : url.replace(/(\/+)?$/, '/'), // add trailing slash if there’s no extension
- base
- ).href;
+export function canonicalURL(url: string, base?: string): URL {
+ let pathname = url.replace(/\/index.html$/, ''); // index.html is not canonical
+ pathname = pathname.replace(/\/1\/?$/, ''); // neither is a trailing /1/ (impl. detail of collections)
+ if (!path.extname(pathname)) pathname = pathname.replace(/(\/+)?$/, '/'); // add trailing slash if there’s no extension
+ return new URL(pathname, base);
}
/** Sort a Set */
diff --git a/packages/astro/src/cli.ts b/packages/astro/src/cli.ts
index 9588b9282..efad9f418 100644
--- a/packages/astro/src/cli.ts
+++ b/packages/astro/src/cli.ts
@@ -65,7 +65,7 @@ function printHelp() {
${colors.bold('Flags:')}
--config <path> Specify the path to the Astro config file.
--project-root <path> Specify the path to the project root folder.
- --no-sitemap Disable sitemap generation (build only).
+ --no-sitemap Disable sitemap generation (build only).
--version Show the version number and exit.
--help Show this help message.
`);
diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts
index e4cfad1cd..6c7e8df07 100644
--- a/packages/astro/src/compiler/codegen/index.ts
+++ b/packages/astro/src/compiler/codegen/index.ts
@@ -3,16 +3,18 @@ import type { AstroConfig, ValidExtensionPlugins } from '../../@types/astro';
import type { Ast, Script, Style, TemplateNode } from 'astro-parser';
import type { TransformResult } from '../../@types/astro';
+import 'source-map-support/register.js';
import eslexer from 'es-module-lexer';
import esbuild from 'esbuild';
import path from 'path';
+import { fileURLToPath } from 'url';
import { walk } from 'estree-walker';
import _babelGenerator from '@babel/generator';
import babelParser from '@babel/parser';
import { codeFrameColumns } from '@babel/code-frame';
import * as babelTraverse from '@babel/traverse';
import { ImportDeclaration, ExportNamedDeclaration, VariableDeclarator, Identifier } from '@babel/types';
-import { warn } from '../../logger.js';
+import { error, warn } from '../../logger.js';
import { fetchContent } from './content.js';
import { isFetchContent } from './utils.js';
import { yellow } from 'kleur/colors';
@@ -77,7 +79,12 @@ function getAttributes(attrs: Attribute[]): Record<string, string> {
switch (val.type) {
case 'MustacheTag': {
// FIXME: this won't work when JSX element can appear in attributes (rare but possible).
- result[attr.name] = '(' + val.expression.codeChunks[0] + ')';
+ const codeChunks = val.expression.codeChunks[0];
+ if (codeChunks) {
+ result[attr.name] = '(' + codeChunks + ')';
+ } else {
+ throw new Error(`Parse error: ${attr.name}={}`); // if bad codeChunk, throw error
+ }
continue;
}
case 'Text':
@@ -388,7 +395,7 @@ function compileModule(module: Script, state: CodegenState, compileOptions: Comp
if (!id || !init || id.type !== 'Identifier') continue;
if (init.type === 'AwaitExpression') {
init = init.argument;
- const shortname = path.relative(compileOptions.astroConfig.projectRoot.pathname, state.filename);
+ const shortname = path.posix.relative(compileOptions.astroConfig.projectRoot.pathname, state.filename);
warn(compileOptions.logging, shortname, yellow('awaiting Astro.fetchContent() not necessary'));
}
if (init.type !== 'CallExpression') continue;
@@ -572,29 +579,36 @@ function compileHtml(enterNode: TemplateNode, state: CodegenState, compileOption
if (!name) {
throw new Error('AHHHH');
}
- const attributes = getAttributes(node.attributes);
+ try {
+ const attributes = getAttributes(node.attributes);
- outSource += outSource === '' ? '' : ',';
- if (node.type === 'Slot') {
- outSource += `(children`;
- return;
- }
- const COMPONENT_NAME_SCANNER = /^[A-Z]/;
- if (!COMPONENT_NAME_SCANNER.test(name)) {
- outSource += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`;
- return;
- }
- const [componentName, componentKind] = name.split(':');
- const componentImportData = components[componentName];
- if (!componentImportData) {
- throw new Error(`Unknown Component: ${componentName}`);
- }
- const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], { astroConfig, dynamicImports, filename });
- if (wrapperImport) {
- importExportStatements.add(wrapperImport);
- }
+ outSource += outSource === '' ? '' : ',';
+ if (node.type === 'Slot') {
+ outSource += `(children`;
+ return;
+ }
+ const COMPONENT_NAME_SCANNER = /^[A-Z]/;
+ if (!COMPONENT_NAME_SCANNER.test(name)) {
+ outSource += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`;
+ return;
+ }
+ const [componentName, componentKind] = name.split(':');
+ const componentImportData = components[componentName];
+ if (!componentImportData) {
+ throw new Error(`Unknown Component: ${componentName}`);
+ }
+ const { wrapper, wrapperImport } = getComponentWrapper(name, components[componentName], { astroConfig, dynamicImports, filename });
+ if (wrapperImport) {
+ importExportStatements.add(wrapperImport);
+ }
- outSource += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
+ outSource += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
+ } catch (err) {
+ // handle errors in scope with filename
+ const rel = filename.replace(fileURLToPath(astroConfig.projectRoot), '');
+ // TODO: return actual codeframe here
+ error(compileOptions.logging, rel, err.toString());
+ }
return;
}
case 'Attribute': {
diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts
index a1a9bde36..f4bfbb19d 100644
--- a/packages/astro/src/compiler/index.ts
+++ b/packages/astro/src/compiler/index.ts
@@ -124,6 +124,7 @@ export async function compileComponent(
{ compileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
): Promise<CompileResult> {
const result = await transformFromSource(source, { compileOptions, filename, projectRoot });
+ const site = compileOptions.astroConfig.buildOptions.site || `http://localhost:${compileOptions.astroConfig.devOptions.port}`;
// return template
let modJsx = `
@@ -137,7 +138,8 @@ import { h, Fragment } from '${internalImport('h.js')}';
const __astroRequestSymbol = Symbol('astro.request');
async function __render(props, ...children) {
const Astro = {
- request: props[__astroRequestSymbol]
+ request: props[__astroRequestSymbol] || {},
+ site: new URL('/', ${JSON.stringify(site)}),
};
${result.script}
diff --git a/packages/astro/src/config.ts b/packages/astro/src/config.ts
index d774d6b9e..682d39ac9 100644
--- a/packages/astro/src/config.ts
+++ b/packages/astro/src/config.ts
@@ -1,5 +1,6 @@
-import 'source-map-support/register.js';
import type { AstroConfig } from './@types/astro';
+
+import 'source-map-support/register.js';
import { join as pathJoin, resolve as pathResolve } from 'path';
import { existsSync } from 'fs';
@@ -9,25 +10,36 @@ const type = (thing: any): string => (Array.isArray(thing) ? 'Array' : typeof th
/** Throws error if a user provided an invalid config. Manually-implemented to avoid a heavy validation library. */
function validateConfig(config: any): void {
// basic
- if (config === undefined || config === null) throw new Error(`[astro config] Config empty!`);
- if (typeof config !== 'object') throw new Error(`[astro config] Expected object, received ${typeof config}`);
+ if (config === undefined || config === null) throw new Error(`[config] Config empty!`);
+ if (typeof config !== 'object') throw new Error(`[config] Expected object, received ${typeof config}`);
// strings
- for (const key of ['projectRoot', 'astroRoot', 'dist', 'public', 'site']) {
+ for (const key of ['projectRoot', 'astroRoot', 'dist', 'public']) {
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'string') {
- throw new Error(`[astro config] ${key}: ${JSON.stringify(config[key])}\n Expected string, received ${type(config[key])}.`);
+ throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected string, received ${type(config[key])}.`);
}
}
// booleans
for (const key of ['sitemap']) {
if (config[key] !== undefined && config[key] !== null && typeof config[key] !== 'boolean') {
- throw new Error(`[astro config] ${key}: ${JSON.stringify(config[key])}\n Expected boolean, received ${type(config[key])}.`);
+ throw new Error(`[config] ${key}: ${JSON.stringify(config[key])}\n Expected boolean, received ${type(config[key])}.`);
+ }
+ }
+
+ // buildOptions
+ if (config.buildOptions && config.buildOptions.site !== undefined) {
+ if (typeof config.buildOptions.site !== 'string') throw new Error(`[config] buildOptions.site is not a string`);
+ try {
+ new URL(config.buildOptions.site);
+ } catch (err) {
+ throw new Error('[config] buildOptions.site must be a valid URL');
}
}
+ // devOptions
if (typeof config.devOptions?.port !== 'number') {
- throw new Error(`[astro config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`);
+ throw new Error(`[config] devOptions.port: Expected number, received ${type(config.devOptions?.port)}`);
}
}
diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts
index 7aa4ba07d..3a366ef00 100644
--- a/packages/astro/src/dev.ts
+++ b/packages/astro/src/dev.ts
@@ -3,9 +3,9 @@ import type { AstroConfig } from './@types/astro';
import type { LogOptions } from './logger.js';
import { logger as snowpackLogger } from 'snowpack';
-import { bold, green } from 'kleur/colors';
+import { green } from 'kleur/colors';
import http from 'http';
-import { relative as pathRelative } from 'path';
+import path from 'path';
import { performance } from 'perf_hooks';
import { defaultLogDestination, error, info, parseError } from './logger.js';
import { createRuntime } from './runtime.js';
@@ -63,7 +63,7 @@ export default async function dev(astroConfig: AstroConfig) {
switch (result.type) {
case 'parse-error': {
const err = result.error;
- err.filename = pathRelative(projectRoot.pathname, err.filename);
+ if (err.filename) err.filename = path.posix.relative(projectRoot.pathname, err.filename);
parseError(logging, err);
break;
}
diff --git a/packages/astro/src/logger.ts b/packages/astro/src/logger.ts
index c1b024c0c..282e8506e 100644
--- a/packages/astro/src/logger.ts
+++ b/packages/astro/src/logger.ts
@@ -114,6 +114,10 @@ export function table(opts: LogOptions, columns: number[]) {
/** Pretty format error for display */
export function parseError(opts: LogOptions, err: CompileError) {
+ if (!err.frame) {
+ return error(opts, 'parse-error', err.message || err);
+ }
+
let frame = err.frame
// Switch colons for pipes
.replace(/^([0-9]+)(:)/gm, `${bold('$1')} │`)
diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts
index 76e27516d..965ea641a 100644
--- a/packages/astro/src/runtime.ts
+++ b/packages/astro/src/runtime.ts
@@ -1,14 +1,15 @@
import 'source-map-support/register.js';
-import { fileURLToPath } from 'url';
import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig } from 'snowpack';
-import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro';
-import type { LogOptions } from './logger';
import type { CompileError } from 'astro-parser';
-import { debug, info } from './logger.js';
-import { searchForPage } from './search.js';
+import type { LogOptions } from './logger';
+import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro';
import { existsSync } from 'fs';
+import { fileURLToPath } from 'url';
import { loadConfiguration, logger as snowpackLogger, startServer as startSnowpackServer } from 'snowpack';
+import { canonicalURL } from './build/util.js';
+import { debug, info } from './logger.js';
+import { searchForPage } from './search.js';
// We need to use require.resolve for snowpack plugins, so create a require function here.
import { createRequire } from 'module';
@@ -49,9 +50,10 @@ snowpackLogger.level = 'silent';
/** Pass a URL to Astro to resolve and build */
async function load(config: RuntimeConfig, rawPathname: string | undefined): Promise<LoadResult> {
const { logging, backendSnowpackRuntime, frontendSnowpack } = config;
- const { astroRoot } = config.astroConfig;
+ const { astroRoot, buildOptions, devOptions } = config.astroConfig;
- const fullurl = new URL(rawPathname || '/', 'https://example.org/');
+ let origin = buildOptions.site ? new URL(buildOptions.site).origin : `http://localhost:${devOptions.port}`;
+ const fullurl = new URL(rawPathname || '/', origin);
const reqPath = decodeURI(fullurl.pathname);
info(logging, 'access', reqPath);
@@ -208,6 +210,7 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
request: {
// params should go here when implemented
url: requestURL,
+ canonicalURL: canonicalURL(requestURL.pathname, requestURL.origin),
},
children: [],
props: { collection },
diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js
new file mode 100644
index 000000000..891e1cfb2
--- /dev/null
+++ b/packages/astro/test/astro-global.test.js
@@ -0,0 +1,45 @@
+import { suite } from 'uvu';
+import * as assert from 'uvu/assert';
+import { doc } from './test-utils.js';
+import { setup } from './helpers.js';
+
+const Global = suite('Astro.*');
+
+setup(Global, './fixtures/astro-global');
+
+Global('Astro.request.url', async (context) => {
+ const result = await context.runtime.load('/');
+
+ assert.equal(result.statusCode, 200);
+
+ const $ = doc(result.contents);
+ assert.equal($('#pathname').text(), '/');
+});
+
+Global('Astro.request.canonicalURL', async (context) => {
+ // given a URL, expect the following canonical URL
+ const canonicalURLs = {
+ '/': 'https://mysite.dev/',
+ '/post/post': 'https://mysite.dev/post/post/',
+ '/posts': 'https://mysite.dev/posts/',
+ '/posts/1': 'https://mysite.dev/posts/', // should be the same as /posts
+ '/posts/2': 'https://mysite.dev/posts/2/',
+ };
+
+ for (const [url, canonicalURL] of Object.entries(canonicalURLs)) {
+ const result = await context.runtime.load(url);
+ const $ = doc(result.contents);
+ assert.equal($('link[rel="canonical"]').attr('href'), canonicalURL);
+ }
+});
+
+Global('Astro.site', async (context) => {
+ const result = await context.runtime.load('/');
+
+ assert.equal(result.statusCode, 200);
+
+ const $ = doc(result.contents);
+ assert.equal($('#site').attr('href'), 'https://mysite.dev');
+});
+
+Global.run();
diff --git a/packages/astro/test/astro-request.test.js b/packages/astro/test/astro-request.test.js
deleted file mode 100644
index 1156714dd..000000000
--- a/packages/astro/test/astro-request.test.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { suite } from 'uvu';
-import * as assert from 'uvu/assert';
-import { doc } from './test-utils.js';
-import { setup } from './helpers.js';
-
-const Request = suite('Astro.request');
-
-setup(Request, './fixtures/astro-request');
-
-Request('Astro.request available', async (context) => {
- const result = await context.runtime.load('/');
-
- assert.equal(result.statusCode, 200);
-
- const $ = doc(result.contents);
- assert.equal($('h1').text(), '/');
-});
-
-Request.run();
diff --git a/packages/astro/test/fixtures/astro-global/astro.config.mjs b/packages/astro/test/fixtures/astro-global/astro.config.mjs
new file mode 100644
index 000000000..a618e1ba3
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/astro.config.mjs
@@ -0,0 +1,6 @@
+export default {
+ buildOptions: {
+ site: 'https://mysite.dev',
+ sitemap: false,
+ },
+};
diff --git a/packages/astro/test/fixtures/astro-global/src/layouts/post.astro b/packages/astro/test/fixtures/astro-global/src/layouts/post.astro
new file mode 100644
index 000000000..4d75a3c28
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/layouts/post.astro
@@ -0,0 +1,12 @@
+---
+export let content;
+---
+<html>
+ <head>
+ <title>{content.title}</title>
+ <link rel="canonical" href={Astro.request.canonicalURL.href}>
+ </head>
+ <body>
+ <slot></slot>
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/astro-global/src/pages/$posts.astro b/packages/astro/test/fixtures/astro-global/src/pages/$posts.astro
new file mode 100644
index 000000000..2384f6ba9
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/pages/$posts.astro
@@ -0,0 +1,28 @@
+---
+export let collection;
+
+export function createCollection() {
+ return {
+ async data() {
+ const data = Astro.fetchContent('./post/*.md');
+ return data;
+ },
+ pageSize: 1,
+ };
+}
+---
+
+<html>
+ <head>
+ <title>All Posts</title>
+ <link rel="canonical" href={Astro.request.canonicalURL.href} />
+ </head>
+ <body>
+ {collection.data.map((data) => (
+ <div>
+ <h1>{data.title}</h1>
+ <a href={data.url}>Read</a>
+ </div>
+ ))}
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/astro-global/src/pages/index.astro b/packages/astro/test/fixtures/astro-global/src/pages/index.astro
new file mode 100644
index 000000000..43b0ee9a6
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/pages/index.astro
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Test</title>
+ <link rel="canonical" href={Astro.request.canonicalURL.href}>
+</head>
+<body>
+ <div id="pathname">{Astro.request.url.pathname}</div>
+ <a id="site" href={Astro.site.origin}>Home</a>
+</body>
+</html>
diff --git a/packages/astro/test/fixtures/astro-global/src/pages/post/post-2.md b/packages/astro/test/fixtures/astro-global/src/pages/post/post-2.md
new file mode 100644
index 000000000..e98f2ec79
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/pages/post/post-2.md
@@ -0,0 +1,6 @@
+---
+title: 'My Post 2'
+layout: '../../layouts/post.astro'
+---
+
+# Post 2
diff --git a/packages/astro/test/fixtures/astro-global/src/pages/post/post.md b/packages/astro/test/fixtures/astro-global/src/pages/post/post.md
new file mode 100644
index 000000000..dd65da76f
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-global/src/pages/post/post.md
@@ -0,0 +1,6 @@
+---
+title: 'My Post'
+layout: '../../layouts/post.astro'
+---
+
+# My Post
diff --git a/packages/astro/test/fixtures/astro-request/src/pages/index.astro b/packages/astro/test/fixtures/astro-request/src/pages/index.astro
deleted file mode 100644
index f809a76e3..000000000
--- a/packages/astro/test/fixtures/astro-request/src/pages/index.astro
+++ /dev/null
@@ -1,10 +0,0 @@
----
-let path = Astro.request.url.pathname;
----
-
-<html>
-<head><title>Test</title></head>
-<body>
- <h1>{path}</h1>
-</body>
-</html> \ No newline at end of file