src/loadScriptFile.ts (92 lines of code) (raw):
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.
import * as retry from 'p-retry';
import * as path from 'path';
import * as url from 'url';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { AzFuncSystemError } from './errors';
import { PackageJson } from './parsers/parsePackageJson';
import { worker } from './WorkerContext';
import LogCategory = rpc.RpcLog.RpcLogCategory;
import LogLevel = rpc.RpcLog.Level;
let hasLoggedAttempt = 0;
let hasLoggedWarning = false;
export async function loadScriptFile(filePath: string, packageJson: PackageJson): Promise<unknown> {
// See the following issue for more details on why we want to retry
// https://github.com/Azure/azure-functions-nodejs-worker/issues/693
const retries = 9;
return await retry(
async (currentAttempt: number) => {
if (currentAttempt > 1 && currentAttempt > hasLoggedAttempt) {
worker.log({
message: `Retrying file load. Attempt ${currentAttempt}/${retries + 1}`,
level: LogLevel.Debug,
logCategory: LogCategory.System,
});
hasLoggedAttempt = currentAttempt;
}
return loadScriptFileInternal(filePath, packageJson);
},
{
retries: retries,
minTimeout: 500,
onFailedAttempt: (error) => {
if (!/lstat.*home/i.test(error?.message || '')) {
// this will abort the retries if it's an error we don't recognize
throw error;
} else if (error.retriesLeft > 0 && !hasLoggedWarning) {
worker.log({
message: `Warning: Failed to load file with error "${error.message}"`,
level: LogLevel.Warning,
logCategory: LogCategory.System,
});
hasLoggedWarning = true;
}
},
}
);
}
async function loadScriptFileInternal(filePath: string, packageJson: PackageJson): Promise<unknown> {
const start = Date.now();
try {
let script: unknown;
if (isESModule(filePath, packageJson)) {
const fileUrl = url.pathToFileURL(filePath);
if (fileUrl.href) {
// use eval so it doesn't get compiled into a require()
script = await eval('import(fileUrl.href)');
} else {
throw new AzFuncSystemError(`'${filePath}' could not be converted to file URL (${fileUrl.href})`);
}
} else {
script = require(/* webpackIgnore: true */ filePath);
}
return script;
} finally {
warnIfLongLoadTime(filePath, start);
}
}
function warnIfLongLoadTime(filePath: string, start: number): void {
const timeElapsed = Date.now() - start;
const rfpName = 'WEBSITE_RUN_FROM_PACKAGE';
const rfpValue = process.env[rfpName];
if (
timeElapsed > 1000 &&
(rfpValue === undefined || rfpValue === '0') &&
process.env.AZURE_FUNCTIONS_ENVIRONMENT !== 'Development' // don't show in core tools
) {
worker.log({
message: `Loading "${path.basename(filePath)}" took ${timeElapsed}ms`,
level: LogLevel.Warning,
logCategory: LogCategory.System,
});
worker.log({
message: `Set "${rfpName}" to "1" to significantly improve load times. Learn more here: https://aka.ms/AAjon54`,
level: LogLevel.Warning,
logCategory: LogCategory.System,
});
}
}
export function isESModule(filePath: string, packageJson: PackageJson): boolean {
if (filePath.endsWith('.mjs')) {
return true;
}
if (filePath.endsWith('.cjs')) {
return false;
}
return packageJson.type === 'module';
}