Skip to content

Commit

Permalink
chore: esbuild plugin for specifying node protocol import (#7)
Browse files Browse the repository at this point in the history
## Why?

Apply the node specifier to avoid conflicts with user imported
polyfills.

## How?

- Plugin to apply `node:` specifier to the imports
- Pugin to stub unsupported modules

## Tickets?

-
[PLAT-955](https://linear.app/fleekxyz/issue/PLAT-955/investigate-user-issues-with-bundling)

## Contribution checklist?

- [x] The commit messages are detailed
- [x] The `build` command runs locally
- [x] Assets or static content are linked and stored in the project
- [x] You have manually tested
- [x] You have provided tests

## Security checklist?

- [x] Sensitive data has been identified and is being protected properly
- [x] Injection has been prevented (parameterized queries, no eval or
system calls)

## Preview?

Optionally, provide the preview url here

---------

Co-authored-by: Helder Oliveira <[email protected]>
  • Loading branch information
gabrielmpinto and heldrida authored Jul 5, 2024
1 parent ffb4189 commit 19780f6
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 152 deletions.
5 changes: 4 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@
"noChangesDetected": "No changes detected. Skipping deployment",
"typeFunctionCodePath": "Enter the path to the javascript file or folder containing the function code:",
"bundlingCode": "Bundling code",
"transformingCode": "Transforming code",
"uploadCodeToIpfs": "Uploading code to IPFS",
"uploadToIpfsFailed": "Failed to upload to IPFS",
"storageUploadSuccessCid": "The Storage IPFS CID is {cid}",
Expand All @@ -326,5 +327,7 @@
"missingEnvVar": "Environment variable {key} is missing",
"envFileParseError": "Failed to parse environment variables file at {path}",
"envFilePathNotFound": "The environment variables file located at {path} could not be found.",
"unknownBundlingError": "Failed to bundle your function code."
"unknownTransformError": "We had trouble transforming your function code. Try again? If the issue persists, let us know to help us improve!",
"failedToApplyNodeImportProtocol": "The process attempted to automatically apply the \"node:\" protocol for importing native modules but encountered an issue. When importing Node.js native modules, always use the \"node:\" prefix. For instance, use \"node:buffer\" instead of \"buffer\". This provides clarity when built-in Node.js modules must be imported.",
"requireDeprecatedUseES6Syntax": "The use of 'require' is deprecated in this context. Please switch to using ES6 'import' syntax for module imports. For example, change 'require' to 'import <ModuleName> from '<ModulePath>';'. This adjustment is necessary to comply with modern JavaScript standards and improve compatibility with Fleek Functions runtime and environment."
}
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"conf": "^10.2.0",
"dotenv": "^16.0.3",
"esbuild": "^0.21.1",
"esbuild-plugins-node-modules-polyfill": "^1.6.4",
"files-from-path": "^1.0.0",
"glob": "^8.1.0",
"lodash-es": "^4.17.21",
Expand All @@ -60,7 +59,7 @@
"devDependencies": {
"@changesets/cli": "^2.27.6",
"@fleek-platform/errors": "^2.1.1",
"@fleek-platform/sdk": "^2.1.4",
"@fleek-platform/sdk": "^2.1.5",
"@fleek-platform/tester": "^2.3.0",
"@fleek-platform/utils-gateways": "^0.1.2",
"@fleek-platform/utils-github": "^0.0.2",
Expand Down
13 changes: 11 additions & 2 deletions src/commands/functions/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cliProgress from 'cli-progress';
import fs from 'fs';

import { output } from '../../cli';
import { SdkGuardedFunction } from '../../guards/types';
Expand All @@ -24,7 +25,7 @@ const deployAction: SdkGuardedFunction<DeployActionArgs> = async ({ sdk, args })
const env = getEnvironmentVariables({ env: args.env, envFile: args.envFile });
const functionToDeploy = await getFunctionOrPrompt({ name: args.name, sdk });
const filePath = await getFunctionPathOrPrompt({ path: args.filePath });
const bundledFilePath = await getCodeFromPath({ path: filePath, noBundle: args.noBundle ?? false, env });
const bundledFilePath = await getCodeFromPath({ filePath, bundle: !!!args.noBundle, env });

output.printNewLine();

Expand All @@ -41,7 +42,15 @@ const deployAction: SdkGuardedFunction<DeployActionArgs> = async ({ sdk, args })
uploadResult = await sdk.storage().uploadPrivateFile({ filePath: bundledFilePath, onUploadProgress: uploadOnProgress(progressBar) });
} else {
const fileLikeObject = await getFileLikeObject(bundledFilePath);
uploadResult = await sdk.storage().uploadFile({ file: fileLikeObject, options: { functionName: functionToDeploy.name }, onUploadProgress: uploadOnProgress(progressBar) });
uploadResult = await sdk.storage().uploadFile({
file: fileLikeObject,
options: { functionName: functionToDeploy.name },
onUploadProgress: uploadOnProgress(progressBar),
});
}

if (!output.debugEnabled) {
fs.rmSync(bundledFilePath);
}

if (!uploadResult.pin.cid) {
Expand Down
60 changes: 60 additions & 0 deletions src/commands/functions/plugins/nodeProtocolImportSpecifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { PluginBuild } from 'esbuild';
import fs from 'fs';

import { supportedRuntimeModules, unsupportedRuntimeModules } from '../runtimeModules';

const replaceLineByMatchRegExpr = ({ contents, moduleName }: { contents: string; moduleName: string }) => {
const reImportSyntax = new RegExp(`import\\s*[\\w\\W]*?\\s*from\\s+["']${moduleName}["']`, 'g');
const reModuleName = new RegExp(`["']${moduleName}["']`, 'g');
const convention = `"node:${moduleName}"`;
const lns = contents.split('\n');
const res = lns.map((ln) => {
const shouldReplace = reImportSyntax.test(ln);

if (!shouldReplace) {
return ln;
}

return ln.replace(reModuleName, convention);
});

return res.join('\n');
};

const applyNodeProtocolConvention = async ({ path }: { path: string }) => {
const buffer = await fs.promises.readFile(path, 'utf8');
const contents = buffer.toString();

const output = [...supportedRuntimeModules, ...unsupportedRuntimeModules].reduce((acc, moduleName) => {
return replaceLineByMatchRegExpr({
contents: acc,
moduleName,
});
}, contents);

return {
contents: output,
};
};

export const nodeProtocolImportSpecifier = ({ onError }: { onError: () => void }) => ({
name: 'nodeProtocolImportSpecifier',
setup(build: PluginBuild) {
build.onLoad({ filter: /\.js$/ }, async ({ path }) => {
try {
const output = await applyNodeProtocolConvention({
path,
});

return output;
} catch (err) {
onError();
}
});

build.onResolve({ filter: /^node:/ }, (args) => ({
path: args.path,
external: true,
}));
},
});
42 changes: 42 additions & 0 deletions src/commands/functions/plugins/unsupportedModuleStub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { OnResolveArgs, Plugin, PluginBuild } from 'esbuild';

import { unsupportedRuntimeModules } from '../runtimeModules';

const unsupportedModules = unsupportedRuntimeModules.map((it) => `node:${it}`);

type ModuleCheckerArgs = {
unsupportedModulesUsed: Set<string>;
};

export const moduleChecker: (args: ModuleCheckerArgs) => Plugin = (args) => {
const { unsupportedModulesUsed } = args;

return {
name: 'moduleChecker',
setup: (build: PluginBuild) => {
build.onLoad({ filter: /.*/, namespace: 'unsupported' }, (args) => {
console.log('unsupported', args);

return {
contents: `
throw new Error('Unsupported module: ${args.path}');
`,
loader: 'js',
};
});

build.onResolve({ filter: /.*/ }, ({ path }: OnResolveArgs) => {
if (unsupportedModules.includes(path) || unsupportedModules.includes(`node:${path}`)) {
unsupportedModulesUsed.add(path);

return {
path,
namespace: 'unsupported',
};
}

return null;
});
},
};
};
55 changes: 55 additions & 0 deletions src/commands/functions/runtimeModules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Worker supported modules
// due to environment constraints
// optimized for edge computing
export const supportedRuntimeModules = [
'buffer',
'crypto',
'domain',
'events',
'http',
'https',
'path',
'punycode',
'stream',
'string_decoder',
'url',
'util',
'zlib',
];

export const unsupportedRuntimeModules = [
'assert/strict',
'child_process',
'cluster',
'constants',
'dgram',
'diagnostics_channel',
'dns',
'fs',
'fs/promises',
'http2',
'inspector',
'module',
'net',
'os',
'path/posix',
'path/win32',
'perf_hooks',
'process',
'querystring',
'readline',
'repl',
'stream/promises',
'stream/web',
'sys',
'timers',
'timers/promises',
'tls',
'trace_events',
'tty',
'v8',
'vm',
'wasi',
'webcrypto',
'worker_threads',
];
Loading

0 comments on commit 19780f6

Please sign in to comment.