Skip to content

Commit c225c44

Browse files
First Maybe-Working Commit
1 parent 4f24b14 commit c225c44

File tree

10 files changed

+162
-121
lines changed

10 files changed

+162
-121
lines changed

.eslintrc.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,5 @@
44
"@techmmunity/eslint-config/common",
55
"@techmmunity/eslint-config/typescript",
66
"@techmmunity/eslint-config/jest"
7-
],
8-
"rules": {
9-
"no-console": "off",
10-
"no-warning-comments": "off"
11-
}
7+
]
128
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-vercel-ncc",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"main": "index.js",
55
"types": "index.d.ts",
66
"license": "Apache-2.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
2+
/* eslint-disable @typescript-eslint/no-require-imports */
3+
import { getRootPath } from "@techmmunity/utils";
4+
import * as Ncc from "@vercel/ncc";
5+
import { FunctionDefinitionHandler } from "serverless";
6+
import { Context } from "types/context";
7+
8+
import { writeZip } from "utils/zip/write";
9+
10+
interface CompileAndZipParams {
11+
context: Context;
12+
funcName: string;
13+
serverlessFolderPath: string;
14+
}
15+
16+
/**
17+
*
18+
*
19+
*
20+
*/
21+
22+
const getExternalModules = () => {
23+
const packageJson = require(getRootPath("package.json"));
24+
25+
const externals = [
26+
...Object.keys(packageJson.dependencies),
27+
...Object.keys(packageJson.devDependencies),
28+
];
29+
30+
return externals;
31+
};
32+
33+
/**
34+
*
35+
*
36+
*
37+
*/
38+
39+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
40+
const getOriginalFilePath = (handler: any) => {
41+
// Check if handler is a well-formed path based handler.
42+
const handlerEntries = /(.*)\..*?$/.exec(handler);
43+
44+
// Get the file path
45+
const [, handlerEntry] = handlerEntries!;
46+
47+
return getRootPath(`${handlerEntry}.ts`);
48+
};
49+
50+
/**
51+
*
52+
*
53+
*
54+
*/
55+
56+
export const compileAndZip = ({
57+
funcName,
58+
context,
59+
serverlessFolderPath,
60+
}: CompileAndZipParams) => {
61+
const externals = getExternalModules();
62+
63+
const func = context.serverless.service.functions[
64+
funcName
65+
] as FunctionDefinitionHandler;
66+
67+
const originalFilePath = getOriginalFilePath(func.handler);
68+
69+
return Ncc(originalFilePath, {
70+
externals,
71+
quiet: true,
72+
minify: true,
73+
}).then(({ code }) =>
74+
writeZip({
75+
fileName: funcName,
76+
content: code,
77+
outputPath: serverlessFolderPath,
78+
}).then(() => {
79+
// Only sets the artifact if successfully writes the zip file
80+
func.package = {
81+
artifact: `${serverlessFolderPath}/${funcName}.zip`,
82+
};
83+
}),
84+
);
85+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { mkdirSync, existsSync, rmSync } from "fs";
2+
3+
export const createServerlessFolder = (path: string) => {
4+
if (existsSync(path)) {
5+
rmSync(path, {
6+
recursive: true,
7+
force: true,
8+
});
9+
}
10+
11+
mkdirSync(path);
12+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FunctionDefinitionImage } from "serverless";
2+
import { Context } from "types/context";
3+
4+
const isNodeRuntime = (runtime: string) => runtime.match(/node/);
5+
6+
export const getAllNodeFunctions = (context: Context) => {
7+
const functions = context.serverless.service.getAllFunctions();
8+
9+
return functions.filter(funcName => {
10+
const func = context.serverless.service.getFunction(funcName);
11+
12+
const funcAsImage = func as FunctionDefinitionImage;
13+
14+
/*
15+
* If `uri` is provided or simple remote image path, it means the
16+
* image isn't built by Serverless so we shouldn't take care of it
17+
*/
18+
if (typeof funcAsImage.image === "string") {
19+
return false;
20+
}
21+
22+
return isNodeRuntime(
23+
func.runtime || context.serverless.service.provider.runtime || "nodejs",
24+
);
25+
});
26+
};

src/hooks/create-artifacts/index.ts

+25-106
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,36 @@
11
/* eslint-disable no-await-in-loop */
2-
/* eslint-disable @typescript-eslint/no-require-imports */
3-
/* eslint-disable @typescript-eslint/no-var-requires */
4-
5-
import Serverless, {
6-
FunctionDefinitionHandler,
7-
FunctionDefinitionImage,
8-
} from "serverless";
9-
import Ncc from "@vercel/ncc";
10-
import { mkdirSync } from "fs";
112

123
import { chunk, getRootPath } from "@techmmunity/utils";
13-
import { writeZip } from "utils/zip/write";
14-
15-
const isNodeRuntime = (runtime: string) => runtime.match(/node/);
16-
17-
const getAllNodeFunctions = (serverless: Serverless) => {
18-
const functions = serverless.service.getAllFunctions();
19-
20-
return functions.filter(funcName => {
21-
const func = serverless.service.getFunction(funcName);
22-
23-
const funcAsImage = func as FunctionDefinitionImage;
24-
25-
/*
26-
* If `uri` is provided or simple remote image path, it means the
27-
* image isn't built by Serverless so we shouldn't take care of it
28-
*/
29-
if (typeof funcAsImage.image === "string") {
30-
return false;
31-
}
32-
33-
return isNodeRuntime(
34-
func.runtime || serverless.service.provider.runtime || "nodejs",
35-
);
36-
});
37-
};
38-
39-
const getHandlerFile = (handler: any) => {
40-
// Check if handler is a well-formed path based handler.
41-
const handlerEntries = /(.*)\..*?$/.exec(handler);
42-
// TODO Remove
43-
console.log("handlerEntries", handlerEntries);
44-
45-
const [, handlerEntry] = handlerEntries!;
46-
47-
return handlerEntry;
48-
};
4+
import { Context } from "types/context";
5+
import { getAllNodeFunctions } from "./helpers/get-all-node-functions";
6+
import { createServerlessFolder } from "./helpers/create-serverless-folder";
7+
import { compileAndZip } from "./helpers/compile-and-zip";
498

509
const CONCURRENCY = 3;
5110

52-
export const createArtifacts = async (serverless: Serverless) => {
53-
const packageJson = require(getRootPath("package.json"));
54-
55-
// TODO Remove
56-
console.log("packageJson", packageJson);
11+
export const createArtifacts = async (context: Context) => {
12+
const functions = getAllNodeFunctions(context);
5713

58-
const externals = [
59-
...Object.keys(packageJson.dependencies),
60-
...Object.keys(packageJson.devDependencies),
61-
];
62-
// TODO Remove
63-
console.log("externals", externals);
64-
65-
const functions = getAllNodeFunctions(serverless);
66-
// TODO Remove
67-
console.log("functions", functions);
14+
const serverlessFolderPath = getRootPath(".serverless");
6815

69-
const chunks = chunk(functions, CONCURRENCY);
16+
createServerlessFolder(serverlessFolderPath);
7017

71-
const results: Array<any> = [];
18+
const concurrency =
19+
context.serverless.service.custom?.ncc?.concurrency || CONCURRENCY;
7220

73-
const serverlessFolderPath = getRootPath(".serverless");
74-
// TODO Remove
75-
console.log("serverlessFolderPath", serverlessFolderPath);
21+
const chunks = chunk(functions, concurrency);
7622

77-
mkdirSync(serverlessFolderPath);
23+
const results: Array<PromiseSettledResult<any>> = [];
7824

7925
for (const c of chunks) {
8026
const result = await Promise.allSettled(
81-
c.map(funcName => {
82-
const func = serverless.service.getFunction(
27+
c.map(funcName =>
28+
compileAndZip({
29+
context,
8330
funcName,
84-
) as FunctionDefinitionHandler;
85-
86-
const handlerFile = getHandlerFile(func.handler);
87-
// TODO Remove
88-
console.log("handlerFile", handlerFile);
89-
90-
const file = `./${handlerFile}.ts`;
91-
92-
/**
93-
* Promise chain is necessary
94-
*/
95-
return Ncc(file, {
96-
externals,
97-
quiet: true,
98-
minify: true,
99-
}).then(({ code }) => {
100-
const fileName = file.split("/").pop()!.split(".").shift()!;
101-
// TODO Remove
102-
console.log("fileName", fileName);
103-
104-
return writeZip({
105-
fileName,
106-
content: code,
107-
outputPath: serverlessFolderPath,
108-
}).then(() => {
109-
// TODO Remove
110-
console.log("artifact", `${serverlessFolderPath}/${fileName}.zip`);
111-
// Only sets the artifact if successfully writes the zip file
112-
func.package = {
113-
artifact: `${serverlessFolderPath}/${fileName}.zip`,
114-
};
115-
});
116-
});
117-
}),
31+
serverlessFolderPath,
32+
}),
33+
),
11834
);
11935

12036
results.push(...result);
@@ -123,9 +39,12 @@ export const createArtifacts = async (serverless: Serverless) => {
12339
/**
12440
* Log all errors
12541
*/
126-
results.filter(Boolean).forEach(() => {
127-
serverless.cli.log("Fail to compile a file", undefined, {
128-
color: "red",
42+
results
43+
.filter(({ status }) => status !== "fulfilled")
44+
.forEach((err: any) => {
45+
context.serverless.cli.log("Fail to compile a file", undefined, {
46+
color: "red",
47+
});
48+
console.error(err);
12949
});
130-
});
13150
};

src/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ class ServerlessVercelNcc {
88
public readonly serverless: Serverless,
99
public readonly options: Options,
1010
) {
11-
console.log("~~~~~Plugin loaded~~~");
12-
1311
this.hooks = {
14-
"before:deploy:deploy": createArtifacts,
15-
"before:package:package": createArtifacts,
12+
"before:package:createDeploymentArtifacts": () => createArtifacts(this),
13+
"before:deploy:function:packageFunction": () => createArtifacts(this),
1614
};
1715
}
1816
}

src/types/context.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Serverless from "serverless";
2+
3+
export interface Context {
4+
serverless: Serverless;
5+
}

src/types/ncc.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ declare module "@vercel/ncc" {
1414
const compiler: (path: string, options: Options) => Promise<Output>;
1515

1616
// eslint-disable-next-line import/no-default-export
17-
export default compiler;
17+
export = compiler;
1818
}

src/utils/zip/write.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import fs from "fs";
2-
import JSZip from "jszip";
1+
import { createWriteStream } from "fs";
2+
import * as JSZip from "jszip";
33

44
interface WriteParams {
55
fileName: string;
@@ -11,11 +11,11 @@ export const writeZip = ({ fileName, content, outputPath }: WriteParams) =>
1111
new Promise((resolve, reject) => {
1212
const zip = new JSZip();
1313

14-
zip.file(`${fileName}.ts`, content);
14+
zip.file(`${fileName}.js`, content);
1515

1616
zip
1717
.generateNodeStream({ type: "nodebuffer", streamFiles: true })
18-
.pipe(fs.createWriteStream(`${outputPath}/${fileName}.zip`))
18+
.pipe(createWriteStream(`${outputPath}/${fileName}.zip`))
1919
.on("finish", () => resolve(undefined))
2020
.on("error", reject);
2121
});

0 commit comments

Comments
 (0)