Skip to content

Commit

Permalink
Update netlify adapter to integrate includeFiles and excludeFiles opt…
Browse files Browse the repository at this point in the history
…ions (#13194)

* feat: integrated includeFiles and excludeFiles on netlify adapter

* feat: added netlify include and exclude files tests

* feat: changelogs added

* fix: avoid problems with glob file url on windows

* feat: improved JS Docs to include examples and important information

* feat: removed non necessary root path as glob is already absolute

* Apply suggestions from code review

* Update packages/integrations/netlify/src/index.ts

Co-authored-by: Matt Kane <[email protected]>

* Update .changeset/ninety-clouds-judge.md

Co-authored-by: Sarah Rainsberger <[email protected]>

---------

Co-authored-by: Emanuele Stoppa <[email protected]>
Co-authored-by: Matt Kane <[email protected]>
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
4 people authored Feb 12, 2025
1 parent b019150 commit 1b5037b
Show file tree
Hide file tree
Showing 15 changed files with 487 additions and 3 deletions.
35 changes: 35 additions & 0 deletions .changeset/ninety-clouds-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
'@astrojs/netlify': minor
---

Adds `includedFiles` and `excludedFiles` configuration options to customize SSR function bundle contents.


The `includeFiles` property allows you to explicitly specify additional files that should be bundled with your function. This is useful for files that aren't automatically detected as dependencies, such as:
- Data files loaded using `fs` operations
- Configuration files
- Template files

Similarly, you can use the `excludeFiles` property to prevent specific files from being bundled that would otherwise be included. This is helpful for:
- Reducing bundle size
- Excluding large binaries
- Preventing unwanted files from being deployed

```js
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';

export default defineConfig({
// ...
output: 'server',
adapter: netlify({
includeFiles: ['./my-data.json'],
excludeFiles: [
'./node_modules/package/**/*',
'./src/**/*.test.js'
],
}),
});
```

See the [Netlify adapter documentation](https://docs.astro.build/en/guides/integrations-guide/netlify/#including-or-excluding-files) for detailed usage instructions and examples.
73 changes: 70 additions & 3 deletions packages/integrations/netlify/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { randomUUID } from 'node:crypto';
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
import type { IncomingMessage } from 'node:http';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { emptyDir } from '@astrojs/internal-helpers/fs';
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import type { Context } from '@netlify/functions';
Expand All @@ -13,6 +13,7 @@ import type {
IntegrationResolvedRoute,
} from 'astro';
import { build } from 'esbuild';
import glob from 'fast-glob';
import { copyDependenciesToFunction } from './lib/nft.js';
import type { Args } from './ssr-function.js';

Expand Down Expand Up @@ -139,6 +140,32 @@ async function writeNetlifyFrameworkConfig(config: AstroConfig, logger: AstroInt
}

export interface NetlifyIntegrationConfig {
/**
* Force files to be bundled with your SSR function.
* This is useful for including any type of file that is not directly detected by the bundler,
* like configuration files or assets that are dynamically imported at runtime.
*
* Note: File paths are resolved relative to your project's `root`. Absolute paths may not work as expected.
*
* @example
* ```js
* includeFiles: ['./src/data/*.json', './src/locales/*.yml', './src/config/*.yaml']
* ```
*/
includeFiles?: string[];

/**
* Exclude files from the bundling process.
* This is useful for excluding any type of file that is not intended to be bundled with your SSR function,
* such as large assets, temporary files, or sensitive local configuration files.
*
* @example
* ```js
* excludeFiles: ['./src/secret/*.json', './src/temp/*.txt']
* ```
*/
excludeFiles?: string[];

/**
* If enabled, On-Demand-Rendered pages are cached for up to a year.
* This is useful for pages that are not updated often, like a blog post,
Expand Down Expand Up @@ -191,6 +218,8 @@ export default function netlifyIntegration(
let outDir: URL;
let rootDir: URL;
let astroMiddlewareEntryPoint: URL | undefined = undefined;
// Extra files to be merged with `includeFiles` during build
const extraFilesToInclude: URL[] = [];
// Secret used to verify that the caller is the astro-generated edge middleware and not a third-party
const middlewareSecret = randomUUID();

Expand Down Expand Up @@ -246,6 +275,18 @@ export default function netlifyIntegration(
}
}

async function getFilesByGlob(
include: Array<string> = [],
exclude: Array<string> = [],
): Promise<Array<URL>> {
const files = await glob(include, {
cwd: fileURLToPath(rootDir),
absolute: true,
ignore: exclude,
});
return files.map((file) => pathToFileURL(file));
}

async function writeSSRFunction({
notFoundContent,
logger,
Expand All @@ -257,12 +298,38 @@ export default function netlifyIntegration(
}) {
const entry = new URL('./entry.mjs', ssrBuildDir());

const _includeFiles = integrationConfig?.includeFiles || [];
const _excludeFiles = integrationConfig?.excludeFiles || [];

if (finalBuildOutput === 'server') {
// Merge any includes from `vite.assetsInclude
if (_config.vite.assetsInclude) {
const mergeGlobbedIncludes = (globPattern: unknown) => {
if (typeof globPattern === 'string') {
const entries = glob.sync(globPattern).map((p) => pathToFileURL(p));
extraFilesToInclude.push(...entries);
} else if (Array.isArray(globPattern)) {
for (const pattern of globPattern) {
mergeGlobbedIncludes(pattern);
}
}
};

mergeGlobbedIncludes(_config.vite.assetsInclude);
}
}

const includeFiles = (await getFilesByGlob(_includeFiles, _excludeFiles)).concat(
extraFilesToInclude,
);
const excludeFiles = await getFilesByGlob(_excludeFiles);

const { handler } = await copyDependenciesToFunction(
{
entry,
outDir: ssrOutputDir(),
includeFiles: [],
excludeFiles: [],
includeFiles: includeFiles,
excludeFiles: excludeFiles,
logger,
root,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import netlify from '@astrojs/netlify';
import { defineConfig } from 'astro/config';

export default defineConfig({
output: 'server',
adapter: netlify(),
site: "http://example.com",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,2,3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,2,3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,2,3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@test/netlify-includes",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/netlify": "workspace:",
"cowsay": "1.6.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
export const prerender = false
const header = Astro.request.headers.get("x-test")
---

<p>This is my custom 404 page</p>
<p>x-test: {header}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
import { promises as fs } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const loadFile = Astro.url.searchParams.get('file');
const file = await fs.readFile(join(__dirname, `../../../files/${loadFile}`), 'utf-8');
async function moo() {
const cow = await import('cowsay');
return cow.say({ text: 'Moo!' });
}
if (Astro.url.searchParams.get('moo')) {
await moo();
}
---
<html>
<head><title>Testing</title></head>
<body>
{loadFile && <h1>{file}</h1>}
</body>
</html>
Loading

0 comments on commit 1b5037b

Please sign in to comment.