@netlify/edge-bundler 2.8.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,7 +78,7 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
78
78
  if (!featureFlags.edge_functions_config_export) {
79
79
  return {};
80
80
  }
81
- return getFunctionConfig(func, deno, logger);
81
+ return getFunctionConfig(func, importMap, deno, logger);
82
82
  }));
83
83
  // Creating a hash of function names to configuration objects.
84
84
  const functionsWithConfig = functions.reduce((acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }), {});
@@ -1,7 +1,13 @@
1
1
  import { DenoBridge } from './bridge.js';
2
2
  import { EdgeFunction } from './edge_function.js';
3
+ import { ImportMap } from './import_map.js';
3
4
  import { Logger } from './logger.js';
5
+ export declare const enum Mode {
6
+ BeforeCache = "before-cache",
7
+ AfterCache = "after-cache"
8
+ }
4
9
  export interface FunctionConfig {
10
+ mode?: Mode;
5
11
  path?: string;
6
12
  }
7
- export declare const getFunctionConfig: (func: EdgeFunction, deno: DenoBridge, log: Logger) => Promise<FunctionConfig>;
13
+ export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger) => Promise<FunctionConfig>;
@@ -19,7 +19,7 @@ const getConfigExtractor = () => {
19
19
  const configExtractorPath = join(packagePath, 'deno', 'config.ts');
20
20
  return configExtractorPath;
21
21
  };
22
- export const getFunctionConfig = async (func, deno, log) => {
22
+ export const getFunctionConfig = async (func, importMap, deno, log) => {
23
23
  // The extractor is a Deno script that will import the function and run its
24
24
  // `config` export, if one exists.
25
25
  const extractorPath = getConfigExtractor();
@@ -38,6 +38,7 @@ export const getFunctionConfig = async (func, deno, log) => {
38
38
  '--allow-read',
39
39
  `--allow-write=${collector.path}`,
40
40
  '--quiet',
41
+ `--import-map=${importMap.toDataURL()}`,
41
42
  extractorPath,
42
43
  pathToFileURL(func.path).href,
43
44
  pathToFileURL(collector.path).href,
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { join, resolve } from 'path';
3
+ import { pathToFileURL } from 'url';
3
4
  import del from 'del';
4
5
  import { stub } from 'sinon';
5
6
  import tmp from 'tmp-promise';
@@ -8,6 +9,13 @@ import { fixturesDir } from '../test/util.js';
8
9
  import { DenoBridge } from './bridge.js';
9
10
  import { bundle } from './bundler.js';
10
11
  import { getFunctionConfig } from './config.js';
12
+ import { ImportMap } from './import_map.js';
13
+ const importMapFile = {
14
+ baseURL: new URL('file:///some/path/import-map.json'),
15
+ imports: {
16
+ 'alias:helper': pathToFileURL(join(fixturesDir, 'helper.ts')).toString(),
17
+ },
18
+ };
11
19
  test('`getFunctionConfig` extracts configuration properties from function file', async () => {
12
20
  const { path: tmpDir } = await tmp.dir();
13
21
  const deno = new DenoBridge({
@@ -101,7 +109,7 @@ test('`getFunctionConfig` extracts configuration properties from function file',
101
109
  const config = await getFunctionConfig({
102
110
  name: func.name,
103
111
  path,
104
- }, deno, logger);
112
+ }, new ImportMap([importMapFile]), deno, logger);
105
113
  expect(config).toEqual(func.expectedConfig);
106
114
  if (func.userLog) {
107
115
  expect(logger.user.firstCall.firstArg).toMatch(func.userLog);
@@ -122,9 +130,10 @@ test('Ignores function paths from the in-source `config` function if the feature
122
130
  featureFlags: {
123
131
  edge_functions_produce_eszip: true,
124
132
  },
133
+ importMaps: [importMapFile],
125
134
  });
126
135
  const generatedFiles = await fs.readdir(tmpDir.path);
127
- expect(result.functions.length).toBe(4);
136
+ expect(result.functions.length).toBe(6);
128
137
  expect(generatedFiles.length).toBe(2);
129
138
  const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8');
130
139
  const manifest = JSON.parse(manifestFile);
@@ -155,20 +164,24 @@ test('Loads function paths from the in-source `config` function', async () => {
155
164
  edge_functions_config_export: true,
156
165
  edge_functions_produce_eszip: true,
157
166
  },
167
+ importMaps: [importMapFile],
158
168
  });
159
169
  const generatedFiles = await fs.readdir(tmpDir.path);
160
- expect(result.functions.length).toBe(4);
170
+ expect(result.functions.length).toBe(6);
161
171
  expect(generatedFiles.length).toBe(2);
162
172
  const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8');
163
173
  const manifest = JSON.parse(manifestFile);
164
- const { bundles, routes } = manifest;
174
+ const { bundles, routes, post_cache_routes: postCacheRoutes } = manifest;
165
175
  expect(bundles.length).toBe(1);
166
176
  expect(bundles[0].format).toBe('eszip2');
167
177
  expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
168
- expect(routes.length).toBe(4);
178
+ expect(routes.length).toBe(5);
169
179
  expect(routes[0]).toEqual({ function: 'framework-func2', pattern: '^/framework-func2/?$' });
170
180
  expect(routes[1]).toEqual({ function: 'user-func2', pattern: '^/user-func2/?$' });
171
181
  expect(routes[2]).toEqual({ function: 'framework-func1', pattern: '^/framework-func1/?$' });
172
182
  expect(routes[3]).toEqual({ function: 'user-func1', pattern: '^/user-func1/?$' });
183
+ expect(routes[4]).toEqual({ function: 'user-func3', pattern: '^/user-func3/?$' });
184
+ expect(postCacheRoutes.length).toBe(1);
185
+ expect(postCacheRoutes[0]).toEqual({ function: 'user-func4', pattern: '^/user-func4/?$' });
173
186
  await fs.rmdir(tmpDir.path, { recursive: true });
174
187
  });
@@ -1,6 +1,7 @@
1
1
  import { FunctionConfig } from './config.js';
2
2
  interface BaseDeclaration {
3
3
  function: string;
4
+ mode?: string;
4
5
  name?: string;
5
6
  }
6
7
  declare type DeclarationWithPath = BaseDeclaration & {
@@ -9,7 +9,7 @@ export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig) =>
9
9
  const { path } = (_a = functionsConfig[declaration.function]) !== null && _a !== void 0 ? _a : {};
10
10
  if (path) {
11
11
  functionsVisited.add(declaration.function);
12
- declarations.push({ function: declaration.function, path });
12
+ declarations.push({ ...declaration, path });
13
13
  }
14
14
  else {
15
15
  declarations.push(declaration);
@@ -18,9 +18,9 @@ export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig) =>
18
18
  // Finally, we must create declarations for functions that are not declared
19
19
  // in the TOML at all.
20
20
  for (const name in functionsConfig) {
21
- const { path } = functionsConfig[name];
21
+ const { path, ...config } = functionsConfig[name];
22
22
  if (!functionsVisited.has(name) && path) {
23
- declarations.push({ function: name, path });
23
+ declarations.push({ ...config, function: name, path });
24
24
  }
25
25
  }
26
26
  return declarations;
@@ -17,6 +17,11 @@ interface Manifest {
17
17
  name?: string;
18
18
  pattern: string;
19
19
  }[];
20
+ post_cache_routes: {
21
+ function: string;
22
+ name?: string;
23
+ pattern: string;
24
+ }[];
20
25
  }
21
26
  declare const generateManifest: ({ bundles, declarations, functions }: GenerateManifestOptions) => Manifest;
22
27
  interface WriteManifestOptions {
@@ -4,18 +4,26 @@ import globToRegExp from 'glob-to-regexp';
4
4
  import { getPackageVersion } from './package_json.js';
5
5
  import { nonNullable } from './utils/non_nullable.js';
6
6
  const generateManifest = ({ bundles = [], declarations = [], functions }) => {
7
- const routes = declarations.map((declaration) => {
7
+ const preCacheRoutes = [];
8
+ const postCacheRoutes = [];
9
+ declarations.forEach((declaration) => {
8
10
  const func = functions.find(({ name }) => declaration.function === name);
9
11
  if (func === undefined) {
10
12
  return;
11
13
  }
12
14
  const pattern = getRegularExpression(declaration);
13
15
  const serializablePattern = pattern.source.replace(/\\\//g, '/');
14
- return {
16
+ const route = {
15
17
  function: func.name,
16
18
  name: declaration.name,
17
19
  pattern: serializablePattern,
18
20
  };
21
+ if (declaration.mode === "after-cache" /* Mode.AfterCache */) {
22
+ postCacheRoutes.push(route);
23
+ }
24
+ else {
25
+ preCacheRoutes.push(route);
26
+ }
19
27
  });
20
28
  const manifestBundles = bundles.map(({ extension, format, hash }) => ({
21
29
  asset: hash + extension,
@@ -23,7 +31,8 @@ const generateManifest = ({ bundles = [], declarations = [], functions }) => {
23
31
  }));
24
32
  const manifest = {
25
33
  bundles: manifestBundles,
26
- routes: routes.filter(nonNullable),
34
+ routes: preCacheRoutes.filter(nonNullable),
35
+ post_cache_routes: postCacheRoutes.filter(nonNullable),
27
36
  bundler_version: getPackageVersion(),
28
37
  };
29
38
  return manifest;
@@ -80,3 +80,39 @@ test('Generates a manifest without bundles', () => {
80
80
  expect(manifest.routes).toEqual(expectedRoutes);
81
81
  expect(manifest.bundler_version).toBe(env.npm_package_version);
82
82
  });
83
+ test('Generates a manifest with pre and post-cache routes', () => {
84
+ const bundle1 = {
85
+ extension: '.ext1',
86
+ format: 'format1',
87
+ hash: '123456',
88
+ };
89
+ const bundle2 = {
90
+ extension: '.ext2',
91
+ format: 'format2',
92
+ hash: '654321',
93
+ };
94
+ const functions = [
95
+ { name: 'func-1', path: '/path/to/func-1.ts' },
96
+ { name: 'func-2', path: '/path/to/func-2.ts' },
97
+ { name: 'func-3', path: '/path/to/func-3.ts' },
98
+ ];
99
+ const declarations = [
100
+ { function: 'func-1', path: '/f1' },
101
+ { function: 'func-2', mode: 'not_a_supported_mode', path: '/f2' },
102
+ { function: 'func-3', mode: 'after-cache', path: '/f3' },
103
+ ];
104
+ const manifest = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
105
+ const expectedBundles = [
106
+ { asset: bundle1.hash + bundle1.extension, format: bundle1.format },
107
+ { asset: bundle2.hash + bundle2.extension, format: bundle2.format },
108
+ ];
109
+ const expectedPreCacheRoutes = [
110
+ { function: 'func-1', name: undefined, pattern: '^/f1/?$' },
111
+ { function: 'func-2', name: undefined, pattern: '^/f2/?$' },
112
+ ];
113
+ const expectedPostCacheRoutes = [{ function: 'func-3', name: undefined, pattern: '^/f3/?$' }];
114
+ expect(manifest.bundles).toEqual(expectedBundles);
115
+ expect(manifest.routes).toEqual(expectedPreCacheRoutes);
116
+ expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes);
117
+ expect(manifest.bundler_version).toBe(env.npm_package_version);
118
+ });
@@ -2,6 +2,7 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
+ /// <reference types="node" />
5
6
  import { OnAfterDownloadHook, OnBeforeDownloadHook } from '../bridge.js';
6
7
  import type { EdgeFunction } from '../edge_function.js';
7
8
  import { ImportMapFile } from '../import_map.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "2.8.0",
3
+ "version": "3.0.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -53,7 +53,7 @@
53
53
  "@commitlint/config-conventional": "^16.0.0",
54
54
  "@netlify/eslint-config-node": "^4.1.7",
55
55
  "@types/glob-to-regexp": "^0.4.1",
56
- "@types/node": "^17.0.21",
56
+ "@types/node": "^14.18.32",
57
57
  "@types/semver": "^7.3.9",
58
58
  "@types/sinon": "^10.0.8",
59
59
  "@types/uuid": "^8.3.4",
@@ -68,7 +68,7 @@
68
68
  "vitest": "^0.24.0"
69
69
  },
70
70
  "engines": {
71
- "node": "^12.20.0 || ^14.14.0 || >=16.0.0"
71
+ "node": "^14.16.0 || >=16.0.0"
72
72
  },
73
73
  "dependencies": {
74
74
  "@import-maps/resolve": "^1.0.1",