Address feedback from review

This commit is contained in:
Chris 2023-08-29 14:55:01 +02:00
parent 97511dd714
commit 30003d7b54
7 changed files with 70 additions and 232 deletions

View file

@ -111,10 +111,10 @@ export default defineConfig({
#### `beforeSend`
Since functions can't be passed down via `astro.config.mjs`, you need to export the `beforeSend` function in a separate file inside your root called `vercel-web-analytics.ts`.
To define the `beforeSend` function, you need to create a separate file inside your root called `vercel-web-analytics.ts`.
If you're not using TypeScript, you can define the function inside `vercel-web-analytics.js`.
```js
```ts
// vercel-web-analytics.ts
import type { VercelWebAnalyticsBeforeSend } from '@astrojs/vercel';
@ -128,8 +128,6 @@ export const beforeSend: VercelWebAnalyticsBeforeSend = (event) => {
}
```
````js
### Speed Insights
You can enable [Vercel Speed Insights](https://vercel.com/docs/concepts/speed-insights) by setting `speedInsights: { enabled: true }`. This will collect and send Web Vital data to Vercel.

View file

@ -52,7 +52,7 @@
},
"dependencies": {
"@astrojs/internal-helpers": "workspace:*",
"@vercel/analytics": "~1.0.0",
"@vercel/analytics": "^1.0.2",
"@vercel/nft": "^0.23.1",
"esbuild": "^0.19.2",
"fast-glob": "^3.3.1",

View file

@ -1,186 +0,0 @@
import type { AstroAdapter, AstroConfig, AstroIntegration } from 'astro';
import esbuild from 'esbuild';
import { relative as relativePath } from 'node:path';
import { fileURLToPath } from 'node:url';
import { defaultImageConfig, getImageConfig, type VercelImageConfig } from '../image/shared.js';
import {
copyFilesToFunction,
getFilesFromFolder,
getVercelOutput,
removeDir,
writeJson,
} from '../lib/fs.js';
import { getRedirects } from '../lib/redirects.js';
import {
getInjectableWebAnalyticsContent,
type VercelWebAnalyticsConfig,
} from '../lib/web-analytics.js';
import {
getSpeedInsightsViteConfig,
type VercelSpeedInsightsConfig,
} from '../lib/speed-insights.js';
const PACKAGE_NAME = '@astrojs/vercel/edge';
function getAdapter(): AstroAdapter {
return {
name: PACKAGE_NAME,
serverEntrypoint: `${PACKAGE_NAME}/entrypoint`,
exports: ['default'],
};
}
export interface VercelEdgeConfig {
webAnalytics?: VercelWebAnalyticsConfig;
speedInsights?: VercelSpeedInsightsConfig;
includeFiles?: string[];
imageService?: boolean;
imagesConfig?: VercelImageConfig;
}
export default function vercelEdge({
webAnalytics,
speedInsights,
includeFiles = [],
imageService,
imagesConfig,
}: VercelEdgeConfig = {}): AstroIntegration {
let _config: AstroConfig;
let buildTempFolder: URL;
let functionFolder: URL;
let serverEntry: string;
return {
name: PACKAGE_NAME,
hooks: {
'astro:config:setup': async ({ command, config, updateConfig, injectScript }) => {
if (webAnalytics?.enabled) {
injectScript(
'page',
await getInjectableWebAnalyticsContent(
{
...webAnalytics.config,
mode: command === 'dev' ? 'development' : 'production',
},
config.root
)
);
}
if (command === 'build' && speedInsights?.enabled) {
injectScript('page', 'import "@astrojs/vercel/speed-insights"');
}
const outDir = getVercelOutput(config.root);
updateConfig({
outDir,
build: {
serverEntry: 'entry.mjs',
client: new URL('./static/', outDir),
server: new URL('./dist/', config.root),
},
vite: {
...getSpeedInsightsViteConfig(speedInsights?.enabled),
ssr: {
external: ['@vercel/nft'],
},
},
...getImageConfig(imageService, imagesConfig, command),
});
},
'astro:config:done': ({ setAdapter, config }) => {
setAdapter(getAdapter());
_config = config;
buildTempFolder = config.build.server;
functionFolder = new URL('./functions/render.func/', config.outDir);
serverEntry = config.build.serverEntry;
if (config.output === 'static') {
throw new Error(`
[@astrojs/vercel] \`output: "server"\` or \`output: "hybrid"\` is required to use the edge adapter.
`);
}
},
'astro:build:setup': ({ vite, target }) => {
if (target === 'server') {
vite.resolve ||= {};
vite.resolve.alias ||= {};
const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
if (Array.isArray(vite.resolve.alias)) {
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
} else {
for (const alias of aliases) {
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
}
}
vite.ssr ||= {};
vite.ssr.target = 'webworker';
// Vercel edge runtime is a special webworker-ish environment that supports process.env,
// but Vite would replace away `process.env` in webworkers, so we set a define here to prevent it
vite.define = {
'process.env': 'process.env',
...vite.define,
};
}
},
'astro:build:done': async ({ routes }) => {
const entry = new URL(serverEntry, buildTempFolder);
const generatedFiles = await getFilesFromFolder(buildTempFolder);
const entryPath = fileURLToPath(entry);
await esbuild.build({
target: 'es2020',
platform: 'browser',
// https://runtime-keys.proposal.wintercg.org/#edge-light
conditions: ['edge-light', 'worker', 'browser'],
entryPoints: [entryPath],
outfile: entryPath,
allowOverwrite: true,
format: 'esm',
bundle: true,
minify: true,
});
// Copy entry and other server files
const commonAncestor = await copyFilesToFunction(
[...generatedFiles, ...includeFiles.map((file) => new URL(file, _config.root))],
functionFolder
);
// Remove temporary folder
await removeDir(buildTempFolder);
// Edge function config
// https://vercel.com/docs/build-output-api/v3#vercel-primitives/edge-functions/configuration
await writeJson(new URL(`./.vc-config.json`, functionFolder), {
runtime: 'edge',
entrypoint: relativePath(commonAncestor, entryPath),
});
// Output configuration
// https://vercel.com/docs/build-output-api/v3#build-output-configuration
await writeJson(new URL(`./config.json`, _config.outDir), {
version: 3,
routes: [
...getRedirects(routes, _config),
{
src: `^/${_config.build.assets}/(.*)$`,
headers: { 'cache-control': 'public, max-age=31536000, immutable' },
continue: true,
},
{ handle: 'filesystem' },
{ src: '/.*', dest: 'render' },
],
...(imageService || imagesConfig
? { images: imagesConfig ? imagesConfig : defaultImageConfig }
: {}),
});
},
},
};
}

View file

@ -1,51 +1,71 @@
import { AstroError } from 'astro/errors';
import type { AnalyticsProps } from '@vercel/analytics';
import { fileURLToPath } from 'url';
import { existsSync } from 'node:fs';
import type { AstroIntegrationLogger } from 'astro';
export type VercelWebAnalyticsConfig = {
enabled: boolean;
config?: Omit<AnalyticsProps, 'beforeSend'>;
};
async function getWebAnalyticsFunctions(root: URL) {
try {
const files = await Promise.all([
import(/* @vite-ignore */ fileURLToPath(new URL('./vercel-web-analytics.ts', root))).catch(
() => undefined
),
import(/* @vite-ignore */ fileURLToPath(new URL('./vercel-web-analytics.js', root))).catch(
() => undefined
),
]);
async function getWebAnalyticsFunctions({
root,
logger,
}: {
root: URL;
logger: AstroIntegrationLogger;
}) {
const tsPath = fileURLToPath(new URL('./vercel-web-analytics.ts', root));
const jsPath = fileURLToPath(new URL('./vercel-web-analytics.js', root));
const functions = files[0] || files[1];
const tsFileExists = existsSync(tsPath);
const jsFileExists = existsSync(jsPath);
if (functions?.default) {
if (typeof functions.default.beforeSend !== 'function') {
throw new Error(
`@astrojs/vercel: ./vercel-web-analytics.js should export a \`beforeSend\` function.`
);
}
if (tsFileExists && jsFileExists) {
logger.warn(
`@astrojs/vercel: Both \`vercel-web-analytics.ts\` and \`vercel-web-analytics.js\` exist. Using \`vercel-web-analytics.ts\`.`
);
}
return {
beforeSend: functions.default.beforeSend,
};
}
if (!tsFileExists && !jsFileExists) {
logger.debug(
`@astrojs/vercel: \`vercel-web-analytics.ts\` or \`vercel-web-analytics.js\` not found.`
);
return {
beforeSend: undefined,
};
} catch (e) {
return {
beforeSend: undefined,
};
}
const functions = await import(
tsFileExists ? /* @vite-ignore */ tsPath : /* @vite-ignore */ jsPath
);
if (typeof functions.beforeSend !== 'function') {
throw new AstroError(
`@astrojs/vercel: \`vercel-web-analytics.${
tsFileExists ? 'ts' : 'js'
}\` must export a \`beforeSend\` function.`
);
}
return {
beforeSend: functions.beforeSend,
};
}
export async function getInjectableWebAnalyticsContent(
config: Omit<AnalyticsProps, 'beforeSend'> | undefined,
root: URL
) {
const { beforeSend } = await getWebAnalyticsFunctions(root);
export async function getInjectableWebAnalyticsContent({
config,
astro,
}: {
config: Omit<AnalyticsProps, 'beforeSend'> | undefined;
astro: {
root: URL;
logger: AstroIntegrationLogger;
};
}) {
const { beforeSend } = await getWebAnalyticsFunctions(astro);
return `import { inject } from '@vercel/analytics';
inject({

View file

@ -116,17 +116,20 @@ export default function vercelServerless({
return {
name: PACKAGE_NAME,
hooks: {
'astro:config:setup': async ({ command, config, updateConfig, injectScript }) => {
'astro:config:setup': async ({ command, config, updateConfig, injectScript, logger }) => {
if (webAnalytics?.enabled) {
injectScript(
'page',
await getInjectableWebAnalyticsContent(
{
await getInjectableWebAnalyticsContent({
config: {
...webAnalytics.config,
mode: command === 'dev' ? 'development' : 'production',
},
config.root
)
astro: {
root: config.root,
logger,
},
})
);
}
if (command === 'build' && speedInsights?.enabled) {

View file

@ -37,17 +37,20 @@ export default function vercelStatic({
return {
name: '@astrojs/vercel',
hooks: {
'astro:config:setup': async ({ command, config, injectScript, updateConfig }) => {
'astro:config:setup': async ({ command, config, injectScript, updateConfig, logger }) => {
if (webAnalytics?.enabled) {
injectScript(
'page',
await getInjectableWebAnalyticsContent(
{
await getInjectableWebAnalyticsContent({
config: {
...webAnalytics.config,
mode: command === 'dev' ? 'development' : 'production',
},
config.root
)
astro: {
root: config.root,
logger,
},
})
);
}
if (command === 'build' && speedInsights?.enabled) {

View file

@ -4679,7 +4679,7 @@ importers:
specifier: workspace:*
version: link:../../internal-helpers
'@vercel/analytics':
specifier: ~1.0.0
specifier: ^1.0.2
version: 1.0.2
'@vercel/nft':
specifier: ^0.23.1