@netlify/edge-bundler 4.0.2 → 4.2.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.
- package/deno/lib/consts.ts +5 -4
- package/deno/lib/stage2.ts +5 -2
- package/dist/node/bundle_error.d.ts +3 -0
- package/dist/node/bundle_error.js +3 -0
- package/dist/node/bundler.d.ts +3 -1
- package/dist/node/bundler.js +2 -1
- package/dist/node/bundler.test.js +51 -0
- package/dist/node/formats/eszip.js +2 -1
- package/dist/node/formats/javascript.js +3 -2
- package/dist/node/layer.d.ts +4 -0
- package/dist/node/layer.js +1 -0
- package/dist/node/manifest.d.ts +10 -3
- package/dist/node/manifest.js +4 -3
- package/dist/node/manifest.test.js +26 -0
- package/dist/node/npm_import_error.d.ts +5 -0
- package/dist/node/npm_import_error.js +20 -0
- package/package.json +1 -1
package/deno/lib/consts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
1
|
+
export const CUSTOM_LAYER_PREFIX = 'layer:'
|
|
2
|
+
export const PUBLIC_SPECIFIER = 'netlify:edge'
|
|
3
|
+
export const STAGE1_SPECIFIER = 'netlify:bootstrap-stage1'
|
|
4
|
+
export const STAGE2_SPECIFIER = 'netlify:bootstrap-stage2'
|
|
5
|
+
export const virtualRoot = 'file:///root/'
|
package/deno/lib/stage2.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.28.0/mod.ts'
|
|
|
3
3
|
import * as path from 'https://deno.land/std@0.127.0/path/mod.ts'
|
|
4
4
|
|
|
5
5
|
import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts'
|
|
6
|
-
import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER, virtualRoot } from './consts.ts'
|
|
6
|
+
import { CUSTOM_LAYER_PREFIX, PUBLIC_SPECIFIER, STAGE2_SPECIFIER, virtualRoot } from './consts.ts'
|
|
7
7
|
import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'
|
|
8
8
|
|
|
9
9
|
interface FunctionReference {
|
|
@@ -70,7 +70,7 @@ const stage2Loader = (basePath: string, functions: InputFunction[]) => {
|
|
|
70
70
|
return inlineModule(specifier, stage2Entry)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
if (specifier === PUBLIC_SPECIFIER) {
|
|
73
|
+
if (specifier === PUBLIC_SPECIFIER || specifier.startsWith(CUSTOM_LAYER_PREFIX)) {
|
|
74
74
|
return {
|
|
75
75
|
kind: 'external',
|
|
76
76
|
specifier,
|
|
@@ -88,6 +88,9 @@ const stage2Loader = (basePath: string, functions: InputFunction[]) => {
|
|
|
88
88
|
const writeStage2 = async ({ basePath, destPath, functions, importMapURL }: WriteStage2Options) => {
|
|
89
89
|
const loader = stage2Loader(basePath, functions)
|
|
90
90
|
const bytes = await build([STAGE2_SPECIFIER], loader, importMapURL)
|
|
91
|
+
const directory = path.dirname(destPath)
|
|
92
|
+
|
|
93
|
+
await Deno.mkdir(directory, { recursive: true })
|
|
91
94
|
|
|
92
95
|
return await Deno.writeFile(destPath, bytes)
|
|
93
96
|
}
|
|
@@ -12,5 +12,8 @@ declare class BundleError extends Error {
|
|
|
12
12
|
customErrorInfo: ReturnType<typeof getCustomErrorInfo>;
|
|
13
13
|
constructor(originalError: Error, options: BundleErrorOptions);
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* BundleErrors are treated as user-error, so Netlify Team is not alerted about them.
|
|
17
|
+
*/
|
|
15
18
|
declare const wrapBundleError: (input: unknown, options: BundleErrorOptions) => unknown;
|
|
16
19
|
export { BundleError, wrapBundleError };
|
|
@@ -15,6 +15,9 @@ class BundleError extends Error {
|
|
|
15
15
|
Object.setPrototypeOf(this, BundleError.prototype);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* BundleErrors are treated as user-error, so Netlify Team is not alerted about them.
|
|
20
|
+
*/
|
|
18
21
|
const wrapBundleError = (input, options) => {
|
|
19
22
|
if (input instanceof Error) {
|
|
20
23
|
return new BundleError(input, options);
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Declaration } from './declaration.js';
|
|
|
3
3
|
import { EdgeFunction } from './edge_function.js';
|
|
4
4
|
import { FeatureFlags } from './feature_flags.js';
|
|
5
5
|
import { ImportMapFile } from './import_map.js';
|
|
6
|
+
import { Layer } from './layer.js';
|
|
6
7
|
import { LogFunction } from './logger.js';
|
|
7
8
|
interface BundleOptions {
|
|
8
9
|
basePath?: string;
|
|
@@ -11,11 +12,12 @@ interface BundleOptions {
|
|
|
11
12
|
distImportMapPath?: string;
|
|
12
13
|
featureFlags?: FeatureFlags;
|
|
13
14
|
importMaps?: ImportMapFile[];
|
|
15
|
+
layers?: Layer[];
|
|
14
16
|
onAfterDownload?: OnAfterDownloadHook;
|
|
15
17
|
onBeforeDownload?: OnBeforeDownloadHook;
|
|
16
18
|
systemLogger?: LogFunction;
|
|
17
19
|
}
|
|
18
|
-
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, onAfterDownload, onBeforeDownload, systemLogger, }?: BundleOptions) => Promise<{
|
|
20
|
+
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, layers, onAfterDownload, onBeforeDownload, systemLogger, }?: BundleOptions) => Promise<{
|
|
19
21
|
functions: EdgeFunction[];
|
|
20
22
|
manifest: import("./manifest.js").Manifest;
|
|
21
23
|
}>;
|
package/dist/node/bundler.js
CHANGED
|
@@ -35,7 +35,7 @@ const createBundle = ({ basePath, buildID, debug, deno, distDirectory, functions
|
|
|
35
35
|
importMap,
|
|
36
36
|
});
|
|
37
37
|
};
|
|
38
|
-
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, onAfterDownload, onBeforeDownload, systemLogger, } = {}) => {
|
|
38
|
+
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, layers, onAfterDownload, onBeforeDownload, systemLogger, } = {}) => {
|
|
39
39
|
const logger = getLogger(systemLogger, debug);
|
|
40
40
|
const featureFlags = getFlags(inputFeatureFlags);
|
|
41
41
|
const options = {
|
|
@@ -90,6 +90,7 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
90
90
|
declarations,
|
|
91
91
|
distDirectory,
|
|
92
92
|
functions,
|
|
93
|
+
layers,
|
|
93
94
|
});
|
|
94
95
|
if (distImportMapPath) {
|
|
95
96
|
await importMap.writeToFile(distImportMapPath);
|
|
@@ -132,6 +132,28 @@ test('Adds a custom error property to user errors during bundling', async () =>
|
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
});
|
|
135
|
+
test('Prints a nice error message when user tries importing NPM module', async () => {
|
|
136
|
+
expect.assertions(2);
|
|
137
|
+
const sourceDirectory = resolve(fixturesDir, 'imports_npm_module', 'functions');
|
|
138
|
+
const tmpDir = await tmp.dir();
|
|
139
|
+
const declarations = [
|
|
140
|
+
{
|
|
141
|
+
function: 'func1',
|
|
142
|
+
path: '/func1',
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
try {
|
|
146
|
+
await bundle([sourceDirectory], tmpDir.path, declarations, {
|
|
147
|
+
featureFlags: {
|
|
148
|
+
edge_functions_produce_eszip: true,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
expect(error).toBeInstanceOf(BundleError);
|
|
154
|
+
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported in Deno via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/p-retry"'?`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
135
157
|
test('Does not add a custom error property to system errors during bundling', async () => {
|
|
136
158
|
expect.assertions(1);
|
|
137
159
|
try {
|
|
@@ -278,3 +300,32 @@ test('Ignores any user-defined `deno.json` files', async () => {
|
|
|
278
300
|
})).not.toThrow();
|
|
279
301
|
await deleteAsync([tmpDir.path, denoConfigPath, importMapFile.path], { force: true });
|
|
280
302
|
});
|
|
303
|
+
test('Processes a function that imports a custom layer', async () => {
|
|
304
|
+
const sourceDirectory = resolve(fixturesDir, 'with_layers', 'functions');
|
|
305
|
+
const tmpDir = await tmp.dir();
|
|
306
|
+
const declarations = [
|
|
307
|
+
{
|
|
308
|
+
function: 'func1',
|
|
309
|
+
path: '/func1',
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
const layer = { name: 'test', flag: 'edge-functions-layer-test' };
|
|
313
|
+
const result = await bundle([sourceDirectory], tmpDir.path, declarations, {
|
|
314
|
+
basePath: fixturesDir,
|
|
315
|
+
featureFlags: {
|
|
316
|
+
edge_functions_produce_eszip: true,
|
|
317
|
+
},
|
|
318
|
+
layers: [layer],
|
|
319
|
+
});
|
|
320
|
+
const generatedFiles = await fs.readdir(tmpDir.path);
|
|
321
|
+
expect(result.functions.length).toBe(1);
|
|
322
|
+
expect(generatedFiles.length).toBe(2);
|
|
323
|
+
const manifestFile = await fs.readFile(resolve(tmpDir.path, 'manifest.json'), 'utf8');
|
|
324
|
+
const manifest = JSON.parse(manifestFile);
|
|
325
|
+
const { bundles, layers } = manifest;
|
|
326
|
+
expect(bundles.length).toBe(1);
|
|
327
|
+
expect(bundles[0].format).toBe('eszip2');
|
|
328
|
+
expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
|
|
329
|
+
expect(layers).toEqual([layer]);
|
|
330
|
+
await fs.rmdir(tmpDir.path, { recursive: true });
|
|
331
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { wrapBundleError } from '../bundle_error.js';
|
|
3
|
+
import { wrapNpmImportError } from '../npm_import_error.js';
|
|
3
4
|
import { getPackagePath } from '../package_json.js';
|
|
4
5
|
import { getFileHash } from '../utils/sha256.js';
|
|
5
6
|
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, functions, importMap, }) => {
|
|
@@ -23,7 +24,7 @@ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, func
|
|
|
23
24
|
await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true });
|
|
24
25
|
}
|
|
25
26
|
catch (error) {
|
|
26
|
-
throw wrapBundleError(error, { format: 'eszip' });
|
|
27
|
+
throw wrapBundleError(wrapNpmImportError(error), { format: 'eszip' });
|
|
27
28
|
}
|
|
28
29
|
const hash = await getFileHash(destPath);
|
|
29
30
|
return { extension, format: 'eszip2', hash };
|
|
@@ -4,8 +4,9 @@ import { env } from 'process';
|
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { deleteAsync } from 'del';
|
|
6
6
|
import { wrapBundleError } from '../bundle_error.js';
|
|
7
|
+
import { wrapNpmImportError } from '../npm_import_error.js';
|
|
7
8
|
import { getFileHash } from '../utils/sha256.js';
|
|
8
|
-
const BOOTSTRAP_LATEST = 'https://
|
|
9
|
+
const BOOTSTRAP_LATEST = 'https://63760359c1267a000900d7fb--edge.netlify.com/bootstrap/index-combined.ts';
|
|
9
10
|
const bundleJS = async ({ buildID, debug, deno, distDirectory, functions, importMap, }) => {
|
|
10
11
|
const stage2Path = await generateStage2({ distDirectory, functions, fileName: `${buildID}-pre.js` });
|
|
11
12
|
const extension = '.js';
|
|
@@ -18,7 +19,7 @@ const bundleJS = async ({ buildID, debug, deno, distDirectory, functions, import
|
|
|
18
19
|
await deno.run(['bundle', ...flags, stage2Path, jsBundlePath], { pipeOutput: true });
|
|
19
20
|
}
|
|
20
21
|
catch (error) {
|
|
21
|
-
throw wrapBundleError(error, { format: 'javascript' });
|
|
22
|
+
throw wrapBundleError(wrapNpmImportError(error), { format: 'javascript' });
|
|
22
23
|
}
|
|
23
24
|
await fs.unlink(stage2Path);
|
|
24
25
|
const hash = await getFileHash(jsBundlePath);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/node/manifest.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Bundle } from './bundle.js';
|
|
2
2
|
import type { Declaration } from './declaration.js';
|
|
3
3
|
import { EdgeFunction } from './edge_function.js';
|
|
4
|
+
import { Layer } from './layer.js';
|
|
4
5
|
interface GenerateManifestOptions {
|
|
5
6
|
bundles?: Bundle[];
|
|
6
|
-
functions: EdgeFunction[];
|
|
7
7
|
declarations?: Declaration[];
|
|
8
|
+
functions: EdgeFunction[];
|
|
9
|
+
layers?: Layer[];
|
|
8
10
|
}
|
|
9
11
|
interface Manifest {
|
|
10
12
|
bundler_version: string;
|
|
@@ -22,13 +24,18 @@ interface Manifest {
|
|
|
22
24
|
name?: string;
|
|
23
25
|
pattern: string;
|
|
24
26
|
}[];
|
|
27
|
+
layers: {
|
|
28
|
+
name: string;
|
|
29
|
+
flag: string;
|
|
30
|
+
}[];
|
|
25
31
|
}
|
|
26
|
-
declare const generateManifest: ({ bundles, declarations, functions }: GenerateManifestOptions) => Manifest;
|
|
32
|
+
declare const generateManifest: ({ bundles, declarations, functions, layers }: GenerateManifestOptions) => Manifest;
|
|
27
33
|
interface WriteManifestOptions {
|
|
28
34
|
bundles: Bundle[];
|
|
29
35
|
declarations: Declaration[];
|
|
30
36
|
distDirectory: string;
|
|
31
37
|
functions: EdgeFunction[];
|
|
38
|
+
layers?: Layer[];
|
|
32
39
|
}
|
|
33
|
-
declare const writeManifest: ({ bundles, declarations, distDirectory, functions }: WriteManifestOptions) => Promise<Manifest>;
|
|
40
|
+
declare const writeManifest: ({ bundles, declarations, distDirectory, functions, layers, }: WriteManifestOptions) => Promise<Manifest>;
|
|
34
41
|
export { generateManifest, Manifest, writeManifest };
|
package/dist/node/manifest.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import globToRegExp from 'glob-to-regexp';
|
|
4
4
|
import { getPackageVersion } from './package_json.js';
|
|
5
5
|
import { nonNullable } from './utils/non_nullable.js';
|
|
6
|
-
const generateManifest = ({ bundles = [], declarations = [], functions }) => {
|
|
6
|
+
const generateManifest = ({ bundles = [], declarations = [], functions, layers = [] }) => {
|
|
7
7
|
const preCacheRoutes = [];
|
|
8
8
|
const postCacheRoutes = [];
|
|
9
9
|
declarations.forEach((declaration) => {
|
|
@@ -34,6 +34,7 @@ const generateManifest = ({ bundles = [], declarations = [], functions }) => {
|
|
|
34
34
|
routes: preCacheRoutes.filter(nonNullable),
|
|
35
35
|
post_cache_routes: postCacheRoutes.filter(nonNullable),
|
|
36
36
|
bundler_version: getPackageVersion(),
|
|
37
|
+
layers,
|
|
37
38
|
};
|
|
38
39
|
return manifest;
|
|
39
40
|
};
|
|
@@ -50,8 +51,8 @@ const getRegularExpression = (declaration) => {
|
|
|
50
51
|
const normalizedSource = `^${regularExpression.source}\\/?$`;
|
|
51
52
|
return new RegExp(normalizedSource);
|
|
52
53
|
};
|
|
53
|
-
const writeManifest = async ({ bundles, declarations = [], distDirectory, functions }) => {
|
|
54
|
-
const manifest = generateManifest({ bundles, declarations, functions });
|
|
54
|
+
const writeManifest = async ({ bundles, declarations = [], distDirectory, functions, layers, }) => {
|
|
55
|
+
const manifest = generateManifest({ bundles, declarations, functions, layers });
|
|
55
56
|
const manifestPath = join(distDirectory, 'manifest.json');
|
|
56
57
|
await fs.writeFile(manifestPath, JSON.stringify(manifest));
|
|
57
58
|
return manifest;
|
|
@@ -116,3 +116,29 @@ test('Generates a manifest with pre and post-cache routes', () => {
|
|
|
116
116
|
expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes);
|
|
117
117
|
expect(manifest.bundler_version).toBe(env.npm_package_version);
|
|
118
118
|
});
|
|
119
|
+
test('Generates a manifest with layers', () => {
|
|
120
|
+
const functions = [
|
|
121
|
+
{ name: 'func-1', path: '/path/to/func-1.ts' },
|
|
122
|
+
{ name: 'func-2', path: '/path/to/func-2.ts' },
|
|
123
|
+
];
|
|
124
|
+
const declarations = [
|
|
125
|
+
{ function: 'func-1', path: '/f1/*' },
|
|
126
|
+
{ function: 'func-2', path: '/f2/*' },
|
|
127
|
+
];
|
|
128
|
+
const expectedRoutes = [
|
|
129
|
+
{ function: 'func-1', pattern: '^/f1/.*/?$' },
|
|
130
|
+
{ function: 'func-2', pattern: '^/f2/.*/?$' },
|
|
131
|
+
];
|
|
132
|
+
const layers = [
|
|
133
|
+
{
|
|
134
|
+
name: 'onion',
|
|
135
|
+
flag: 'edge_functions_onion_layer',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
const manifest1 = generateManifest({ bundles: [], declarations, functions });
|
|
139
|
+
const manifest2 = generateManifest({ bundles: [], declarations, functions, layers });
|
|
140
|
+
expect(manifest1.routes).toEqual(expectedRoutes);
|
|
141
|
+
expect(manifest1.layers).toEqual([]);
|
|
142
|
+
expect(manifest2.routes).toEqual(expectedRoutes);
|
|
143
|
+
expect(manifest2.layers).toEqual(layers);
|
|
144
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class NPMImportError extends Error {
|
|
2
|
+
constructor(originalError, moduleName) {
|
|
3
|
+
super(`It seems like you're trying to import an npm module. This is only supported in Deno via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/${moduleName}"'?`);
|
|
4
|
+
this.name = 'NPMImportError';
|
|
5
|
+
this.stack = originalError.stack;
|
|
6
|
+
// https://github.com/microsoft/TypeScript-wiki/blob/8a66ecaf77118de456f7cd9c56848a40fe29b9b4/Breaking-Changes.md#implicit-any-error-raised-for-un-annotated-callback-arguments-with-no-matching-overload-arguments
|
|
7
|
+
Object.setPrototypeOf(this, NPMImportError.prototype);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const wrapNpmImportError = (input) => {
|
|
11
|
+
if (input instanceof Error) {
|
|
12
|
+
const match = input.message.match(/Relative import path "(.*)" not prefixed with/);
|
|
13
|
+
if (match !== null) {
|
|
14
|
+
const [, moduleName] = match;
|
|
15
|
+
return new NPMImportError(input, moduleName);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return input;
|
|
19
|
+
};
|
|
20
|
+
export { NPMImportError, wrapNpmImportError };
|