@netlify/edge-bundler 8.19.0 → 8.20.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/bundle.ts +10 -2
- package/deno/config.ts +4 -2
- package/deno/lib/stage2.ts +21 -4
- package/dist/node/bundle_error.js +4 -0
- package/dist/node/bundler.d.ts +7 -7
- package/dist/node/bundler.js +34 -4
- package/dist/node/bundler.test.js +37 -2
- package/dist/node/config.d.ts +1 -2
- package/dist/node/config.js +1 -2
- package/dist/node/config.test.js +0 -5
- package/dist/node/feature_flags.d.ts +2 -0
- package/dist/node/feature_flags.js +1 -0
- package/dist/node/formats/eszip.d.ts +2 -1
- package/dist/node/formats/eszip.js +9 -6
- package/dist/node/import_map.d.ts +6 -0
- package/dist/node/import_map.js +23 -1
- package/dist/node/import_map.test.js +22 -10
- package/dist/node/npm_dependencies.d.ts +22 -0
- package/dist/node/npm_dependencies.js +189 -0
- package/dist/node/server/server.js +1 -1
- package/dist/shared/consts.d.ts +3 -0
- package/dist/shared/consts.js +3 -0
- package/dist/shared/stage2.d.ts +1 -0
- package/dist/test/util.d.ts +1 -1
- package/dist/test/util.js +5 -2
- package/package.json +2 -1
- package/shared/consts.ts +3 -0
- package/shared/stage2.ts +1 -0
package/deno/bundle.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { writeStage2 } from './lib/stage2.ts'
|
|
2
2
|
|
|
3
3
|
const [payload] = Deno.args
|
|
4
|
-
const { basePath, destPath, externals, functions, importMapData } = JSON.parse(payload)
|
|
4
|
+
const { basePath, destPath, externals, functions, importMapData, vendorDirectory } = JSON.parse(payload)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
try {
|
|
7
|
+
await writeStage2({ basePath, destPath, externals, functions, importMapData, vendorDirectory })
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error instanceof Error && error.message.includes("The module's source code could not be parsed")) {
|
|
10
|
+
delete error.stack
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
throw error
|
|
14
|
+
}
|
package/deno/config.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// this needs to be updated whenever there's a change to globalThis.Netlify in bootstrap
|
|
2
|
+
import { Netlify } from "https://64e8753eae24930008fac6d9--edge.netlify.app/bootstrap/index-combined.ts"
|
|
3
|
+
|
|
4
|
+
const [functionURL, collectorURL, rawExitCodes] = Deno.args
|
|
2
5
|
const exitCodes = JSON.parse(rawExitCodes)
|
|
3
6
|
|
|
4
|
-
const { Netlify } = await import(bootstrapURL)
|
|
5
7
|
globalThis.Netlify = Netlify
|
|
6
8
|
|
|
7
9
|
let func
|
package/deno/lib/stage2.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.40.0/mod.ts'
|
|
|
3
3
|
import * as path from 'https://deno.land/std@0.177.0/path/mod.ts'
|
|
4
4
|
|
|
5
5
|
import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts'
|
|
6
|
-
import { importMapSpecifier, virtualRoot } from '../../shared/consts.ts'
|
|
6
|
+
import { importMapSpecifier, virtualRoot, virtualVendorRoot } from '../../shared/consts.ts'
|
|
7
7
|
import { LEGACY_PUBLIC_SPECIFIER, PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
|
|
8
8
|
import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'
|
|
9
9
|
|
|
@@ -63,7 +63,13 @@ const getVirtualPath = (basePath: string, filePath: string) => {
|
|
|
63
63
|
return url
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const stage2Loader = (
|
|
66
|
+
const stage2Loader = (
|
|
67
|
+
basePath: string,
|
|
68
|
+
functions: InputFunction[],
|
|
69
|
+
externals: Set<string>,
|
|
70
|
+
importMapData: string | undefined,
|
|
71
|
+
vendorDirectory?: string,
|
|
72
|
+
) => {
|
|
67
73
|
return async (specifier: string): Promise<LoadResponse | undefined> => {
|
|
68
74
|
if (specifier === STAGE2_SPECIFIER) {
|
|
69
75
|
const stage2Entry = getStage2Entry(basePath, functions)
|
|
@@ -91,13 +97,24 @@ const stage2Loader = (basePath: string, functions: InputFunction[], externals: S
|
|
|
91
97
|
return loadFromVirtualRoot(specifier, virtualRoot, basePath)
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
if (vendorDirectory !== undefined && specifier.startsWith(virtualVendorRoot)) {
|
|
101
|
+
return loadFromVirtualRoot(specifier, virtualVendorRoot, vendorDirectory)
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
return await loadWithRetry(specifier)
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
const writeStage2 = async ({
|
|
108
|
+
const writeStage2 = async ({
|
|
109
|
+
basePath,
|
|
110
|
+
destPath,
|
|
111
|
+
externals,
|
|
112
|
+
functions,
|
|
113
|
+
importMapData,
|
|
114
|
+
vendorDirectory,
|
|
115
|
+
}: WriteStage2Options) => {
|
|
99
116
|
const importMapURL = importMapData ? importMapSpecifier : undefined
|
|
100
|
-
const loader = stage2Loader(basePath, functions, new Set(externals), importMapData)
|
|
117
|
+
const loader = stage2Loader(basePath, functions, new Set(externals), importMapData, vendorDirectory)
|
|
101
118
|
const bytes = await build([STAGE2_SPECIFIER], loader, importMapURL)
|
|
102
119
|
const directory = path.dirname(destPath)
|
|
103
120
|
|
|
@@ -20,6 +20,10 @@ class BundleError extends Error {
|
|
|
20
20
|
*/
|
|
21
21
|
const wrapBundleError = (input, options) => {
|
|
22
22
|
if (input instanceof Error) {
|
|
23
|
+
if (input.message.includes("The module's source code could not be parsed")) {
|
|
24
|
+
// eslint-disable-next-line no-param-reassign
|
|
25
|
+
input.message = input.stderr;
|
|
26
|
+
}
|
|
23
27
|
return new BundleError(input, options);
|
|
24
28
|
}
|
|
25
29
|
return input;
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { OnAfterDownloadHook, OnBeforeDownloadHook } from './bridge.js';
|
|
2
2
|
import { Declaration } from './declaration.js';
|
|
3
|
+
import { EdgeFunction } from './edge_function.js';
|
|
3
4
|
import { FeatureFlags } from './feature_flags.js';
|
|
4
5
|
import { LogFunction } from './logger.js';
|
|
5
|
-
interface BundleOptions {
|
|
6
|
+
export interface BundleOptions {
|
|
6
7
|
basePath?: string;
|
|
8
|
+
bootstrapURL?: string;
|
|
7
9
|
cacheDirectory?: string;
|
|
8
10
|
configPath?: string;
|
|
9
11
|
debug?: boolean;
|
|
10
12
|
distImportMapPath?: string;
|
|
11
13
|
featureFlags?: FeatureFlags;
|
|
12
14
|
importMapPaths?: (string | undefined)[];
|
|
15
|
+
internalSrcFolder?: string;
|
|
13
16
|
onAfterDownload?: OnAfterDownloadHook;
|
|
14
17
|
onBeforeDownload?: OnBeforeDownloadHook;
|
|
15
18
|
systemLogger?: LogFunction;
|
|
16
|
-
|
|
17
|
-
bootstrapURL?: string;
|
|
19
|
+
vendorDirectory?: string;
|
|
18
20
|
}
|
|
19
|
-
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger,
|
|
20
|
-
functions:
|
|
21
|
+
export declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, internalSrcFolder, onAfterDownload, onBeforeDownload, systemLogger, vendorDirectory, }?: BundleOptions) => Promise<{
|
|
22
|
+
functions: EdgeFunction[];
|
|
21
23
|
manifest: import("./manifest.js").Manifest;
|
|
22
24
|
}>;
|
|
23
|
-
export { bundle };
|
|
24
|
-
export type { BundleOptions };
|
package/dist/node/bundler.js
CHANGED
|
@@ -13,8 +13,9 @@ import { bundle as bundleESZIP } from './formats/eszip.js';
|
|
|
13
13
|
import { ImportMap } from './import_map.js';
|
|
14
14
|
import { getLogger } from './logger.js';
|
|
15
15
|
import { writeManifest } from './manifest.js';
|
|
16
|
+
import { vendorNPMSpecifiers } from './npm_dependencies.js';
|
|
16
17
|
import { ensureLatestTypes } from './types.js';
|
|
17
|
-
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger,
|
|
18
|
+
export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], internalSrcFolder, onAfterDownload, onBeforeDownload, systemLogger, vendorDirectory, } = {}) => {
|
|
18
19
|
const logger = getLogger(systemLogger, debug);
|
|
19
20
|
const featureFlags = getFlags(inputFeatureFlags);
|
|
20
21
|
const options = {
|
|
@@ -46,6 +47,17 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
46
47
|
const userFunctions = userSourceDirectories.length === 0 ? [] : await findFunctions(userSourceDirectories);
|
|
47
48
|
const internalFunctions = internalSrcFolder ? await findFunctions([internalSrcFolder]) : [];
|
|
48
49
|
const functions = [...internalFunctions, ...userFunctions];
|
|
50
|
+
const vendor = await safelyVendorNPMSpecifiers({
|
|
51
|
+
basePath,
|
|
52
|
+
featureFlags,
|
|
53
|
+
functions,
|
|
54
|
+
importMap,
|
|
55
|
+
logger,
|
|
56
|
+
vendorDirectory,
|
|
57
|
+
});
|
|
58
|
+
if (vendor) {
|
|
59
|
+
importMap.add(vendor.importMap);
|
|
60
|
+
}
|
|
49
61
|
const functionBundle = await bundleESZIP({
|
|
50
62
|
basePath,
|
|
51
63
|
buildID,
|
|
@@ -56,6 +68,7 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
56
68
|
functions,
|
|
57
69
|
featureFlags,
|
|
58
70
|
importMap,
|
|
71
|
+
vendorDirectory: vendor === null || vendor === void 0 ? void 0 : vendor.directory,
|
|
59
72
|
});
|
|
60
73
|
// The final file name of the bundles contains a SHA256 hash of the contents,
|
|
61
74
|
// which we can only compute now that the files have been generated. So let's
|
|
@@ -63,8 +76,8 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
63
76
|
await createFinalBundles([functionBundle], distDirectory, buildID);
|
|
64
77
|
// Retrieving a configuration object for each function.
|
|
65
78
|
// Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno
|
|
66
|
-
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger
|
|
67
|
-
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger
|
|
79
|
+
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
|
|
80
|
+
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
|
|
68
81
|
// Creating a hash of function names to configuration objects.
|
|
69
82
|
const internalFunctionsWithConfig = Object.fromEntries(await Promise.all(internalConfigPromises));
|
|
70
83
|
const userFunctionsWithConfig = Object.fromEntries(await Promise.all(userConfigPromises));
|
|
@@ -86,6 +99,7 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
86
99
|
importMap: importMapSpecifier,
|
|
87
100
|
layers: deployConfig.layers,
|
|
88
101
|
});
|
|
102
|
+
await (vendor === null || vendor === void 0 ? void 0 : vendor.cleanup());
|
|
89
103
|
if (distImportMapPath) {
|
|
90
104
|
await importMap.writeToFile(distImportMapPath);
|
|
91
105
|
}
|
|
@@ -133,4 +147,20 @@ const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }) =>
|
|
|
133
147
|
[functionName]: addGeneratorFallback(mergedConfigFields),
|
|
134
148
|
};
|
|
135
149
|
}, {});
|
|
136
|
-
|
|
150
|
+
const safelyVendorNPMSpecifiers = async ({ basePath, featureFlags, functions, importMap, logger, vendorDirectory, }) => {
|
|
151
|
+
if (!featureFlags.edge_functions_npm_modules) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
return await vendorNPMSpecifiers({
|
|
156
|
+
basePath,
|
|
157
|
+
directory: vendorDirectory,
|
|
158
|
+
functions: functions.map(({ path }) => path),
|
|
159
|
+
importMap,
|
|
160
|
+
logger,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
logger.system(error);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
@@ -68,7 +68,8 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy
|
|
|
68
68
|
await cleanup();
|
|
69
69
|
});
|
|
70
70
|
test('Adds a custom error property to user errors during bundling', async () => {
|
|
71
|
-
|
|
71
|
+
process.env.NO_COLOR = 'true';
|
|
72
|
+
expect.assertions(3);
|
|
72
73
|
const { basePath, cleanup, distPath } = await useFixture('invalid_functions');
|
|
73
74
|
const sourceDirectory = join(basePath, 'functions');
|
|
74
75
|
const declarations = [
|
|
@@ -82,6 +83,16 @@ test('Adds a custom error property to user errors during bundling', async () =>
|
|
|
82
83
|
}
|
|
83
84
|
catch (error) {
|
|
84
85
|
expect(error).toBeInstanceOf(BundleError);
|
|
86
|
+
const [messageBeforeStack] = error.message.split('at <anonymous> (file://');
|
|
87
|
+
expect(messageBeforeStack).toMatchInlineSnapshot(`
|
|
88
|
+
"error: Uncaught (in promise) Error: The module's source code could not be parsed: Unexpected eof at file:///root/functions/func1.ts:1:27
|
|
89
|
+
|
|
90
|
+
export default async () =>
|
|
91
|
+
~
|
|
92
|
+
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
|
93
|
+
^
|
|
94
|
+
"
|
|
95
|
+
`);
|
|
85
96
|
expect(error.customErrorInfo).toEqual({
|
|
86
97
|
location: {
|
|
87
98
|
format: 'eszip',
|
|
@@ -109,7 +120,7 @@ test('Prints a nice error message when user tries importing NPM module', async (
|
|
|
109
120
|
}
|
|
110
121
|
catch (error) {
|
|
111
122
|
expect(error).toBeInstanceOf(BundleError);
|
|
112
|
-
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/
|
|
123
|
+
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-1"'?`);
|
|
113
124
|
}
|
|
114
125
|
finally {
|
|
115
126
|
await cleanup();
|
|
@@ -362,3 +373,27 @@ test('Handles imports with the `node:` prefix', async () => {
|
|
|
362
373
|
expect(func1).toBe('ok');
|
|
363
374
|
await cleanup();
|
|
364
375
|
});
|
|
376
|
+
test('Loads npm modules from bare specifiers with and without the `npm:` prefix', async () => {
|
|
377
|
+
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module');
|
|
378
|
+
const sourceDirectory = join(basePath, 'functions');
|
|
379
|
+
const declarations = [
|
|
380
|
+
{
|
|
381
|
+
function: 'func1',
|
|
382
|
+
path: '/func1',
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
const vendorDirectory = await tmp.dir();
|
|
386
|
+
await bundle([sourceDirectory], distPath, declarations, {
|
|
387
|
+
basePath,
|
|
388
|
+
featureFlags: { edge_functions_npm_modules: true },
|
|
389
|
+
importMapPaths: [join(basePath, 'import_map.json')],
|
|
390
|
+
vendorDirectory: vendorDirectory.path,
|
|
391
|
+
});
|
|
392
|
+
const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8');
|
|
393
|
+
const manifest = JSON.parse(manifestFile);
|
|
394
|
+
const bundlePath = join(distPath, manifest.bundles[0].asset);
|
|
395
|
+
const { func1 } = await runESZIP(bundlePath, vendorDirectory.path);
|
|
396
|
+
expect(func1).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
|
|
397
|
+
await cleanup();
|
|
398
|
+
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
399
|
+
});
|
package/dist/node/config.d.ts
CHANGED
|
@@ -19,10 +19,9 @@ export interface FunctionConfig {
|
|
|
19
19
|
generator?: string;
|
|
20
20
|
method?: HTTPMethod | HTTPMethod[];
|
|
21
21
|
}
|
|
22
|
-
export declare const getFunctionConfig: ({ func, importMap, deno,
|
|
22
|
+
export declare const getFunctionConfig: ({ func, importMap, deno, log, }: {
|
|
23
23
|
func: EdgeFunction;
|
|
24
24
|
importMap: ImportMap;
|
|
25
25
|
deno: DenoBridge;
|
|
26
|
-
bootstrapURL: string;
|
|
27
26
|
log: Logger;
|
|
28
27
|
}) => Promise<FunctionConfig>;
|
package/dist/node/config.js
CHANGED
|
@@ -26,7 +26,7 @@ const getConfigExtractor = () => {
|
|
|
26
26
|
const configExtractorPath = join(packagePath, 'deno', 'config.ts');
|
|
27
27
|
return configExtractorPath;
|
|
28
28
|
};
|
|
29
|
-
export const getFunctionConfig = async ({ func, importMap, deno,
|
|
29
|
+
export const getFunctionConfig = async ({ func, importMap, deno, log, }) => {
|
|
30
30
|
// The extractor is a Deno script that will import the function and run its
|
|
31
31
|
// `config` export, if one exists.
|
|
32
32
|
const extractorPath = getConfigExtractor();
|
|
@@ -50,7 +50,6 @@ export const getFunctionConfig = async ({ func, importMap, deno, bootstrapURL, l
|
|
|
50
50
|
extractorPath,
|
|
51
51
|
pathToFileURL(func.path).href,
|
|
52
52
|
pathToFileURL(collector.path).href,
|
|
53
|
-
bootstrapURL,
|
|
54
53
|
JSON.stringify(ConfigExitCode),
|
|
55
54
|
], { rejectOnExitCode: false });
|
|
56
55
|
if (exitCode !== ConfigExitCode.Success) {
|
package/dist/node/config.test.js
CHANGED
|
@@ -9,7 +9,6 @@ import { DenoBridge } from './bridge.js';
|
|
|
9
9
|
import { bundle } from './bundler.js';
|
|
10
10
|
import { getFunctionConfig } from './config.js';
|
|
11
11
|
import { ImportMap } from './import_map.js';
|
|
12
|
-
const bootstrapURL = 'https://edge.netlify.com/bootstrap/index-combined.ts';
|
|
13
12
|
const importMapFile = {
|
|
14
13
|
baseURL: new URL('file:///some/path/import-map.json'),
|
|
15
14
|
imports: {
|
|
@@ -122,7 +121,6 @@ describe('`getFunctionConfig` extracts configuration properties from function fi
|
|
|
122
121
|
importMap: new ImportMap([importMapFile]),
|
|
123
122
|
deno,
|
|
124
123
|
log: logger,
|
|
125
|
-
bootstrapURL,
|
|
126
124
|
});
|
|
127
125
|
if (func.error) {
|
|
128
126
|
await expect(funcCall()).rejects.toThrowError(func.error);
|
|
@@ -246,7 +244,6 @@ test('Passes validation if default export exists and is a function', async () =>
|
|
|
246
244
|
importMap: new ImportMap([importMapFile]),
|
|
247
245
|
deno,
|
|
248
246
|
log: logger,
|
|
249
|
-
bootstrapURL,
|
|
250
247
|
})).resolves.not.toThrow();
|
|
251
248
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
252
249
|
});
|
|
@@ -276,7 +273,6 @@ test('Fails validation if default export is not function', async () => {
|
|
|
276
273
|
importMap: new ImportMap([importMapFile]),
|
|
277
274
|
deno,
|
|
278
275
|
log: logger,
|
|
279
|
-
bootstrapURL,
|
|
280
276
|
});
|
|
281
277
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
282
278
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
@@ -306,7 +302,6 @@ test('Fails validation if default export is not present', async () => {
|
|
|
306
302
|
importMap: new ImportMap([importMapFile]),
|
|
307
303
|
deno,
|
|
308
304
|
log: logger,
|
|
309
|
-
bootstrapURL,
|
|
310
305
|
});
|
|
311
306
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
312
307
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
declare const defaultFlags: {
|
|
2
2
|
edge_functions_fail_unsupported_regex: boolean;
|
|
3
|
+
edge_functions_npm_modules: boolean;
|
|
3
4
|
};
|
|
4
5
|
type FeatureFlag = keyof typeof defaultFlags;
|
|
5
6
|
type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
|
|
6
7
|
declare const getFlags: (input?: Record<string, boolean>, flags?: {
|
|
7
8
|
edge_functions_fail_unsupported_regex: boolean;
|
|
9
|
+
edge_functions_npm_modules: boolean;
|
|
8
10
|
}) => FeatureFlags;
|
|
9
11
|
export { defaultFlags, getFlags };
|
|
10
12
|
export type { FeatureFlag, FeatureFlags };
|
|
@@ -13,6 +13,7 @@ interface BundleESZIPOptions {
|
|
|
13
13
|
featureFlags: FeatureFlags;
|
|
14
14
|
functions: EdgeFunction[];
|
|
15
15
|
importMap: ImportMap;
|
|
16
|
+
vendorDirectory?: string;
|
|
16
17
|
}
|
|
17
|
-
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, }: BundleESZIPOptions) => Promise<Bundle>;
|
|
18
|
+
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
|
|
18
19
|
export { bundleESZIP as bundle };
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { pathToFileURL } from 'url';
|
|
3
|
-
import { virtualRoot } from '../../shared/consts.js';
|
|
3
|
+
import { virtualRoot, virtualVendorRoot } from '../../shared/consts.js';
|
|
4
4
|
import { BundleFormat } from '../bundle.js';
|
|
5
5
|
import { wrapBundleError } from '../bundle_error.js';
|
|
6
6
|
import { wrapNpmImportError } from '../npm_import_error.js';
|
|
7
7
|
import { getPackagePath } from '../package_json.js';
|
|
8
8
|
import { getFileHash } from '../utils/sha256.js';
|
|
9
|
-
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, }) => {
|
|
9
|
+
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }) => {
|
|
10
10
|
const extension = '.eszip';
|
|
11
11
|
const destPath = join(distDirectory, `${buildID}${extension}`);
|
|
12
|
-
const { bundler, importMap: bundlerImportMap } = getESZIPPaths();
|
|
13
|
-
// Transforming all paths under `basePath` to use the virtual root prefix.
|
|
14
12
|
const importMapPrefixes = {
|
|
15
13
|
[`${pathToFileURL(basePath)}/`]: virtualRoot,
|
|
16
14
|
};
|
|
17
|
-
|
|
15
|
+
if (vendorDirectory !== undefined) {
|
|
16
|
+
importMapPrefixes[`${pathToFileURL(vendorDirectory)}/`] = virtualVendorRoot;
|
|
17
|
+
}
|
|
18
|
+
const { bundler, importMap: bundlerImportMap } = getESZIPPaths();
|
|
19
|
+
const importMapData = JSON.stringify(importMap.getContents(importMapPrefixes));
|
|
18
20
|
const payload = {
|
|
19
21
|
basePath,
|
|
20
22
|
destPath,
|
|
21
23
|
externals,
|
|
22
24
|
functions,
|
|
23
|
-
importMapData
|
|
25
|
+
importMapData,
|
|
26
|
+
vendorDirectory,
|
|
24
27
|
};
|
|
25
28
|
const flags = ['--allow-all', '--no-config', `--import-map=${bundlerImportMap}`];
|
|
26
29
|
if (!debug) {
|
|
@@ -14,6 +14,8 @@ export declare class ImportMap {
|
|
|
14
14
|
addFile(path: string, logger: Logger): Promise<void>;
|
|
15
15
|
addFiles(paths: (string | undefined)[], logger: Logger): Promise<void>;
|
|
16
16
|
static applyPrefixesToImports(imports: Imports, prefixes: Record<string, string>): Imports;
|
|
17
|
+
static convertImportsToURLObjects(imports: Imports): Record<string, URL>;
|
|
18
|
+
static convertScopesToURLObjects(scopes: Record<string, Imports>): Record<string, Record<string, URL>>;
|
|
17
19
|
static applyPrefixesToPath(path: string, prefixes: Record<string, string>): string;
|
|
18
20
|
filterImports(imports?: Record<string, URL | null>): Record<string, string>;
|
|
19
21
|
filterScopes(scopes?: ParsedImportMap['scopes']): Record<string, Imports>;
|
|
@@ -21,6 +23,10 @@ export declare class ImportMap {
|
|
|
21
23
|
imports: Imports;
|
|
22
24
|
scopes: {};
|
|
23
25
|
};
|
|
26
|
+
getContentsWithURLObjects(prefixes?: Record<string, string>): {
|
|
27
|
+
imports: Record<string, URL>;
|
|
28
|
+
scopes: Record<string, Record<string, URL>>;
|
|
29
|
+
};
|
|
24
30
|
static readFile(path: string, logger: Logger): Promise<ImportMapFile>;
|
|
25
31
|
resolve(source: ImportMapFile): {
|
|
26
32
|
imports: Record<string, string>;
|
package/dist/node/import_map.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parse } from '@import-maps/resolve';
|
|
|
6
6
|
import { isFileNotFoundError } from './utils/error.js';
|
|
7
7
|
const INTERNAL_IMPORTS = {
|
|
8
8
|
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
9
|
-
'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
|
|
9
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
10
10
|
};
|
|
11
11
|
// ImportMap can take several import map files and merge them into a final
|
|
12
12
|
// import map object, also adding the internal imports in the right order.
|
|
@@ -44,6 +44,18 @@ export class ImportMap {
|
|
|
44
44
|
[key]: ImportMap.applyPrefixesToPath(value, prefixes),
|
|
45
45
|
}), {});
|
|
46
46
|
}
|
|
47
|
+
static convertImportsToURLObjects(imports) {
|
|
48
|
+
return Object.entries(imports).reduce((acc, [key, value]) => ({
|
|
49
|
+
...acc,
|
|
50
|
+
[key]: new URL(value),
|
|
51
|
+
}), {});
|
|
52
|
+
}
|
|
53
|
+
static convertScopesToURLObjects(scopes) {
|
|
54
|
+
return Object.entries(scopes).reduce((acc, [key, value]) => ({
|
|
55
|
+
...acc,
|
|
56
|
+
[key]: ImportMap.convertImportsToURLObjects(value),
|
|
57
|
+
}), {});
|
|
58
|
+
}
|
|
47
59
|
// Applies a list of prefixes to a given path, returning the replaced path.
|
|
48
60
|
// For example, given a `path` of `file:///foo/bar/baz.js` and a `prefixes`
|
|
49
61
|
// object with `{"file:///foo/": "file:///hello/"}`, this method will return
|
|
@@ -122,6 +134,16 @@ export class ImportMap {
|
|
|
122
134
|
scopes: transformedScopes,
|
|
123
135
|
};
|
|
124
136
|
}
|
|
137
|
+
// The same as `getContents`, but the URLs are represented as URL objects
|
|
138
|
+
// instead of strings. This is compatible with the `ParsedImportMap` type
|
|
139
|
+
// from the `@import-maps/resolve` library.
|
|
140
|
+
getContentsWithURLObjects(prefixes = {}) {
|
|
141
|
+
const { imports, scopes } = this.getContents(prefixes);
|
|
142
|
+
return {
|
|
143
|
+
imports: ImportMap.convertImportsToURLObjects(imports),
|
|
144
|
+
scopes: ImportMap.convertScopesToURLObjects(scopes),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
125
147
|
static async readFile(path, logger) {
|
|
126
148
|
const baseURL = pathToFileURL(path);
|
|
127
149
|
try {
|
|
@@ -20,10 +20,16 @@ test('Handles import maps with full URLs without specifying a base URL', () => {
|
|
|
20
20
|
},
|
|
21
21
|
};
|
|
22
22
|
const map = new ImportMap([inputFile1, inputFile2]);
|
|
23
|
-
const
|
|
24
|
-
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
25
|
-
expect(imports['
|
|
26
|
-
expect(imports['alias:
|
|
23
|
+
const m1 = map.getContents();
|
|
24
|
+
expect(m1.imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
25
|
+
expect(m1.imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
26
|
+
expect(m1.imports['alias:jamstack']).toBe('https://jamstack.org/');
|
|
27
|
+
expect(m1.imports['alias:pets']).toBe('https://petsofnetlify.com/');
|
|
28
|
+
const m2 = map.getContentsWithURLObjects();
|
|
29
|
+
expect(m2.imports['netlify:edge']).toStrictEqual(new URL('https://edge.netlify.com/v1/index.ts?v=legacy'));
|
|
30
|
+
expect(m2.imports['@netlify/edge-functions']).toStrictEqual(new URL('https://edge.netlify.com/v1/index.ts'));
|
|
31
|
+
expect(m2.imports['alias:jamstack']).toStrictEqual(new URL('https://jamstack.org/'));
|
|
32
|
+
expect(m2.imports['alias:pets']).toStrictEqual(new URL('https://petsofnetlify.com/'));
|
|
27
33
|
});
|
|
28
34
|
test('Resolves relative paths to absolute paths if a base path is not provided', () => {
|
|
29
35
|
const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
|
|
@@ -34,10 +40,15 @@ test('Resolves relative paths to absolute paths if a base path is not provided',
|
|
|
34
40
|
},
|
|
35
41
|
};
|
|
36
42
|
const map = new ImportMap([inputFile1]);
|
|
37
|
-
const { imports } = map.getContents();
|
|
38
43
|
const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets');
|
|
39
|
-
|
|
40
|
-
expect(imports['
|
|
44
|
+
const m1 = map.getContents();
|
|
45
|
+
expect(m1.imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
46
|
+
expect(m1.imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
47
|
+
expect(m1.imports['alias:pets']).toBe(`${pathToFileURL(expectedPath).toString()}/`);
|
|
48
|
+
const m2 = map.getContentsWithURLObjects();
|
|
49
|
+
expect(m2.imports['netlify:edge']).toStrictEqual(new URL('https://edge.netlify.com/v1/index.ts?v=legacy'));
|
|
50
|
+
expect(m2.imports['@netlify/edge-functions']).toStrictEqual(new URL('https://edge.netlify.com/v1/index.ts'));
|
|
51
|
+
expect(m2.imports['alias:pets']).toStrictEqual(new URL(`${pathToFileURL(expectedPath).toString()}/`));
|
|
41
52
|
});
|
|
42
53
|
describe('Returns the fully resolved import map', () => {
|
|
43
54
|
const inputFile1 = {
|
|
@@ -70,7 +81,7 @@ describe('Returns the fully resolved import map', () => {
|
|
|
70
81
|
specifier2: 'file:///some/full/path/file2.js',
|
|
71
82
|
specifier1: 'file:///some/full/path/file.js',
|
|
72
83
|
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
73
|
-
'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
|
|
84
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
74
85
|
});
|
|
75
86
|
expect(scopes).toStrictEqual({
|
|
76
87
|
'file:///some/cool/path/with/scopes/': {
|
|
@@ -92,7 +103,7 @@ describe('Returns the fully resolved import map', () => {
|
|
|
92
103
|
specifier2: 'file:///root/full/path/file2.js',
|
|
93
104
|
specifier1: 'file:///root/full/path/file.js',
|
|
94
105
|
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
95
|
-
'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
|
|
106
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
96
107
|
});
|
|
97
108
|
expect(scopes).toStrictEqual({
|
|
98
109
|
'file:///root/cool/path/with/scopes/': {
|
|
@@ -128,6 +139,7 @@ test('Writes import map file to disk', async () => {
|
|
|
128
139
|
const { imports } = JSON.parse(createdFile);
|
|
129
140
|
const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets', 'file.ts');
|
|
130
141
|
await file.cleanup();
|
|
131
|
-
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
142
|
+
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
143
|
+
expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
132
144
|
expect(imports['alias:pets']).toBe(pathToFileURL(expectedPath).toString());
|
|
133
145
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ParsedImportMap } from '@import-maps/resolve';
|
|
3
|
+
import { Plugin } from 'esbuild';
|
|
4
|
+
import { ImportMap } from './import_map.js';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
6
|
+
export declare const getDependencyTrackerPlugin: (specifiers: Set<string>, importMap: ParsedImportMap, baseURL: URL) => Plugin;
|
|
7
|
+
interface VendorNPMSpecifiersOptions {
|
|
8
|
+
basePath: string;
|
|
9
|
+
directory?: string;
|
|
10
|
+
functions: string[];
|
|
11
|
+
importMap: ImportMap;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
}
|
|
14
|
+
export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, logger, }: VendorNPMSpecifiersOptions) => Promise<{
|
|
15
|
+
cleanup: () => Promise<void>;
|
|
16
|
+
directory: string;
|
|
17
|
+
importMap: {
|
|
18
|
+
baseURL: import("url").URL;
|
|
19
|
+
imports: Record<string, string>;
|
|
20
|
+
};
|
|
21
|
+
} | undefined>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { builtinModules, createRequire } from 'module';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
|
+
import { resolve } from '@import-maps/resolve';
|
|
6
|
+
import { build } from 'esbuild';
|
|
7
|
+
import tmp from 'tmp-promise';
|
|
8
|
+
import { nodePrefix, npmPrefix } from '../shared/consts.js';
|
|
9
|
+
const builtinModulesSet = new Set(builtinModules);
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
// Workaround for https://github.com/evanw/esbuild/issues/1921.
|
|
12
|
+
const banner = {
|
|
13
|
+
js: `
|
|
14
|
+
import {createRequire as ___nfyCreateRequire} from "module";
|
|
15
|
+
import {fileURLToPath as ___nfyFileURLToPath} from "url";
|
|
16
|
+
import {dirname as ___nfyPathDirname} from "path";
|
|
17
|
+
let __filename=___nfyFileURLToPath(import.meta.url);
|
|
18
|
+
let __dirname=___nfyPathDirname(___nfyFileURLToPath(import.meta.url));
|
|
19
|
+
let require=___nfyCreateRequire(import.meta.url);
|
|
20
|
+
`,
|
|
21
|
+
};
|
|
22
|
+
// esbuild plugin that will traverse the code and look for imports of external
|
|
23
|
+
// dependencies (i.e. Node modules). It stores the specifiers found in the Set
|
|
24
|
+
// provided.
|
|
25
|
+
export const getDependencyTrackerPlugin = (specifiers, importMap, baseURL) => ({
|
|
26
|
+
name: 'dependency-tracker',
|
|
27
|
+
setup(build) {
|
|
28
|
+
build.onResolve({ filter: /^(.*)$/ }, (args) => {
|
|
29
|
+
if (args.kind !== 'import-statement') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const result = {};
|
|
33
|
+
let specifier = args.path;
|
|
34
|
+
// Start by checking whether the specifier matches any import map defined
|
|
35
|
+
// by the user.
|
|
36
|
+
const { matched, resolvedImport } = resolve(specifier, importMap, baseURL);
|
|
37
|
+
// If it does, the resolved import is the specifier we'll evaluate going
|
|
38
|
+
// forward.
|
|
39
|
+
if (matched) {
|
|
40
|
+
specifier = fileURLToPath(resolvedImport).replace(/\\/g, '/');
|
|
41
|
+
result.path = specifier;
|
|
42
|
+
}
|
|
43
|
+
// If the specifier is a Node.js built-in, we don't want to bundle it.
|
|
44
|
+
if (specifier.startsWith(nodePrefix) || builtinModulesSet.has(specifier)) {
|
|
45
|
+
return { external: true };
|
|
46
|
+
}
|
|
47
|
+
// If the specifier has the `npm:` prefix, strip it and use the rest of
|
|
48
|
+
// the specifier to resolve the module.
|
|
49
|
+
if (specifier.startsWith(npmPrefix)) {
|
|
50
|
+
const canonicalPath = specifier.slice(npmPrefix.length);
|
|
51
|
+
return build.resolve(canonicalPath, {
|
|
52
|
+
kind: args.kind,
|
|
53
|
+
resolveDir: args.resolveDir,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const isLocalImport = specifier.startsWith(path.sep) || specifier.startsWith('.');
|
|
57
|
+
// If this is a local import, return so that esbuild visits that path.
|
|
58
|
+
if (isLocalImport) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
const isRemoteURLImport = specifier.startsWith('https://') || specifier.startsWith('http://');
|
|
62
|
+
if (isRemoteURLImport) {
|
|
63
|
+
return { external: true };
|
|
64
|
+
}
|
|
65
|
+
// At this point we know we're dealing with a bare specifier that should
|
|
66
|
+
// be treated as an external module. We first try to resolve it, because
|
|
67
|
+
// in the event that it doesn't exist (e.g. user is referencing a module
|
|
68
|
+
// that they haven't installed) we won't even attempt to bundle it. This
|
|
69
|
+
// lets the ESZIP bundler handle and report the missing import instead of
|
|
70
|
+
// esbuild, which is a better experience for the user.
|
|
71
|
+
try {
|
|
72
|
+
require.resolve(specifier, { paths: [args.resolveDir] });
|
|
73
|
+
specifiers.add(specifier);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// no-op
|
|
77
|
+
}
|
|
78
|
+
// Mark the specifier as external, because we don't want to traverse the
|
|
79
|
+
// entire module tree — i.e. if user code imports module `foo` and that
|
|
80
|
+
// imports `bar`, we only want to add `foo` to the list of specifiers,
|
|
81
|
+
// since the whole module — including its dependencies like `bar` —
|
|
82
|
+
// will be bundled.
|
|
83
|
+
return { external: true };
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, logger, }) => {
|
|
88
|
+
const specifiers = new Set();
|
|
89
|
+
// The directories that esbuild will use when resolving Node modules. We must
|
|
90
|
+
// set these manually because esbuild will be operating from a temporary
|
|
91
|
+
// directory that will not live inside the project root, so the normal
|
|
92
|
+
// resolution logic won't work.
|
|
93
|
+
const nodePaths = [path.join(basePath, 'node_modules')];
|
|
94
|
+
// We need to create some files on disk, which we don't want to write to the
|
|
95
|
+
// project directory. If a custom directory has been specified, which happens
|
|
96
|
+
// only in tests, we use it. Otherwise, create a random temporary directory.
|
|
97
|
+
const temporaryDirectory = directory ? { path: directory } : await tmp.dir();
|
|
98
|
+
// Do a first pass at bundling to gather a list of specifiers that should be
|
|
99
|
+
// loaded as npm dependencies, because they either use the `npm:` prefix or
|
|
100
|
+
// they are bare specifiers. We'll collect them in `specifiers`.
|
|
101
|
+
try {
|
|
102
|
+
await build({
|
|
103
|
+
banner,
|
|
104
|
+
bundle: true,
|
|
105
|
+
entryPoints: functions,
|
|
106
|
+
logLevel: 'error',
|
|
107
|
+
nodePaths,
|
|
108
|
+
outdir: temporaryDirectory.path,
|
|
109
|
+
platform: 'node',
|
|
110
|
+
plugins: [getDependencyTrackerPlugin(specifiers, importMap.getContentsWithURLObjects(), pathToFileURL(basePath))],
|
|
111
|
+
write: false,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.system('Could not track dependencies in edge function:', error);
|
|
116
|
+
logger.user('An error occurred when trying to scan your edge functions for npm modules, which is an experimental feature. If you are loading npm modules, please share the errors above in https://ntl.fyi/edge-functions-npm. If you are not loading npm modules, you can ignore this message.');
|
|
117
|
+
}
|
|
118
|
+
// If we found no specifiers, there's nothing left to do here.
|
|
119
|
+
if (specifiers.size === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
logger.user('You are using npm modules in Edge Functions, which is an experimental feature. Learn more at https://ntl.fyi/edge-functions-npm.');
|
|
123
|
+
// To bundle an entire module and all its dependencies, we create a stub file
|
|
124
|
+
// where we re-export everything from that specifier. We do this for every
|
|
125
|
+
// specifier, and each of these files will be the entry points to esbuild.
|
|
126
|
+
const ops = await Promise.all([...specifiers].map(async (specifier, index) => {
|
|
127
|
+
const code = `import * as mod from "${specifier}"; export default mod.default; export * from "${specifier}";`;
|
|
128
|
+
const filePath = path.join(temporaryDirectory.path, `stub-${index}.js`);
|
|
129
|
+
await fs.writeFile(filePath, code);
|
|
130
|
+
return { filePath, specifier };
|
|
131
|
+
}));
|
|
132
|
+
const entryPoints = ops.map(({ filePath }) => filePath);
|
|
133
|
+
// Bundle each of the stub files we've created. We'll end up with a compiled
|
|
134
|
+
// version of each of the stub files, plus any chunks of shared code between
|
|
135
|
+
// stubs (such that a common module isn't bundled twice).
|
|
136
|
+
await build({
|
|
137
|
+
allowOverwrite: true,
|
|
138
|
+
banner,
|
|
139
|
+
bundle: true,
|
|
140
|
+
entryPoints,
|
|
141
|
+
format: 'esm',
|
|
142
|
+
logLevel: 'error',
|
|
143
|
+
nodePaths,
|
|
144
|
+
outdir: temporaryDirectory.path,
|
|
145
|
+
platform: 'node',
|
|
146
|
+
splitting: true,
|
|
147
|
+
target: 'es2020',
|
|
148
|
+
});
|
|
149
|
+
// Add all Node.js built-ins to the import map, so any unprefixed specifiers
|
|
150
|
+
// (e.g. `process`) resolve to the prefixed versions (e.g. `node:prefix`),
|
|
151
|
+
// which Deno can process.
|
|
152
|
+
const builtIns = builtinModules.reduce((acc, name) => ({
|
|
153
|
+
...acc,
|
|
154
|
+
[name]: `node:${name}`,
|
|
155
|
+
}), {});
|
|
156
|
+
// Creates an object that is compatible with the `imports` block of an import
|
|
157
|
+
// map, mapping specifiers to the paths of their bundled files on disk. Each
|
|
158
|
+
// specifier gets two entries in the import map, one with the `npm:` prefix
|
|
159
|
+
// and one without, such that both options are supported.
|
|
160
|
+
const newImportMap = {
|
|
161
|
+
baseURL: pathToFileURL(temporaryDirectory.path),
|
|
162
|
+
imports: ops.reduce((acc, op) => {
|
|
163
|
+
const url = pathToFileURL(op.filePath).toString();
|
|
164
|
+
return {
|
|
165
|
+
...acc,
|
|
166
|
+
[op.specifier]: url,
|
|
167
|
+
[npmPrefix + op.specifier]: url,
|
|
168
|
+
};
|
|
169
|
+
}, builtIns),
|
|
170
|
+
};
|
|
171
|
+
const cleanup = async () => {
|
|
172
|
+
// If a custom temporary directory was specified, we leave the cleanup job
|
|
173
|
+
// up to the caller.
|
|
174
|
+
if (directory) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await fs.rm(temporaryDirectory.path, { force: true, recursive: true });
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// no-op
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
cleanup,
|
|
186
|
+
directory: temporaryDirectory.path,
|
|
187
|
+
importMap: newImportMap,
|
|
188
|
+
};
|
|
189
|
+
};
|
|
@@ -43,7 +43,7 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
43
43
|
});
|
|
44
44
|
let functionsConfig = [];
|
|
45
45
|
if (options.getFunctionsConfig) {
|
|
46
|
-
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno,
|
|
46
|
+
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, log: logger })));
|
|
47
47
|
}
|
|
48
48
|
const success = await waitForServer(port, processRef.ps);
|
|
49
49
|
return {
|
package/dist/shared/consts.d.ts
CHANGED
package/dist/shared/consts.js
CHANGED
package/dist/shared/stage2.d.ts
CHANGED
package/dist/test/util.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ declare const useFixture: (fixtureName: string) => Promise<{
|
|
|
7
7
|
distPath: string;
|
|
8
8
|
}>;
|
|
9
9
|
declare const getRouteMatcher: (manifest: Manifest) => (candidate: string) => import("../node/manifest.js").Route | undefined;
|
|
10
|
-
declare const runESZIP: (eszipPath: string) => Promise<any>;
|
|
10
|
+
declare const runESZIP: (eszipPath: string, vendorDirectory?: string) => Promise<any>;
|
|
11
11
|
export { fixturesDir, getRouteMatcher, testLogger, runESZIP, useFixture };
|
package/dist/test/util.js
CHANGED
|
@@ -48,7 +48,7 @@ const getRouteMatcher = (manifest) => (candidate) => manifest.routes.find((route
|
|
|
48
48
|
const isExcluded = excludedPatterns.some((pattern) => new RegExp(pattern).test(candidate));
|
|
49
49
|
return !isExcluded;
|
|
50
50
|
});
|
|
51
|
-
const runESZIP = async (eszipPath) => {
|
|
51
|
+
const runESZIP = async (eszipPath, vendorDirectory) => {
|
|
52
52
|
var _a, _b, _c;
|
|
53
53
|
const tmpDir = await tmp.dir({ unsafeCleanup: true });
|
|
54
54
|
// Extract ESZIP into temporary directory.
|
|
@@ -68,7 +68,10 @@ const runESZIP = async (eszipPath) => {
|
|
|
68
68
|
const importMapPath = join(virtualRootPath, '..', 'import-map');
|
|
69
69
|
for (const path of [importMapPath, stage2Path]) {
|
|
70
70
|
const file = await fs.readFile(path, 'utf8');
|
|
71
|
-
|
|
71
|
+
let normalizedFile = file.replace(/file:\/{3}root/g, pathToFileURL(virtualRootPath).toString());
|
|
72
|
+
if (vendorDirectory !== undefined) {
|
|
73
|
+
normalizedFile = normalizedFile.replace(/file:\/{3}vendor/g, pathToFileURL(vendorDirectory).toString());
|
|
74
|
+
}
|
|
72
75
|
await fs.writeFile(path, normalizedFile);
|
|
73
76
|
}
|
|
74
77
|
await fs.rename(stage2Path, `${stage2Path}.js`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.20.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"better-ajv-errors": "^1.2.0",
|
|
80
80
|
"common-path-prefix": "^3.0.0",
|
|
81
81
|
"env-paths": "^3.0.0",
|
|
82
|
+
"esbuild": "0.19.2",
|
|
82
83
|
"execa": "^6.0.0",
|
|
83
84
|
"find-up": "^6.3.0",
|
|
84
85
|
"get-port": "^6.1.2",
|
package/shared/consts.ts
CHANGED