Skip to content
This repository was archived by the owner on Jan 16, 2025. It is now read-only.

Commit 7d9a493

Browse files
walkerburginWalker Burgin
andauthored
Use workers to process files in parallel (#280)
* Bump @types/node to 20.x.x and increase 'engines' check * Add dependency on piscina * Add a worker pool --------- Co-authored-by: Walker Burgin <wburgin@palantir.com>
1 parent 908d0a1 commit 7d9a493

File tree

9 files changed

+159
-64
lines changed

9 files changed

+159
-64
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
},
3939
"homepage": "https://github.com/swc-project/cli#readme",
4040
"engines": {
41-
"node": ">= 12.13"
41+
"node": ">= 16.14.0"
4242
},
4343
"bin": {
4444
"swc": "./bin/swc.js",
@@ -50,6 +50,7 @@
5050
"commander": "^7.1.0",
5151
"fast-glob": "^3.2.5",
5252
"minimatch": "^9.0.3",
53+
"piscina": "^4.3.0",
5354
"semver": "^7.3.8",
5455
"slash": "3.0.0",
5556
"source-map": "^0.7.3"
@@ -59,7 +60,7 @@
5960
"@swc/core": "^1.2.66",
6061
"@swc/jest": "^0.1.2",
6162
"@types/jest": "^29.5.0",
62-
"@types/node": "^12.19.16",
63+
"@types/node": "^20.11.5",
6364
"@types/semver": "^7.3.13",
6465
"chokidar": "^3.5.1",
6566
"deepmerge": "^4.2.2",

src/spack/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const makeDir = promisify(mkdir);
5656
await makeDir(dirname(fullPath), { recursive: true });
5757
await write(fullPath, output[name].code, "utf-8");
5858
if (output[name].map) {
59-
await write(`${fullPath}.map`, output[name].map, "utf-8");
59+
await write(`${fullPath}.map`, output[name].map!, "utf-8");
6060
}
6161
});
6262
} else {

src/swc/__tests__/options.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,32 @@ describe("parserArgs", () => {
130130
expect(mockExit).toHaveBeenCalledWith(2);
131131
expect(mockConsoleError).toHaveBeenCalledTimes(2);
132132
});
133+
134+
it("--workers exits on non-numeric values", async () => {
135+
const args = [
136+
"node",
137+
"/path/to/node_modules/swc-cli/bin/swc.js",
138+
"--workers",
139+
"not-a-number",
140+
"src",
141+
];
142+
await parserArgs(args);
143+
expect(mockExit).toHaveBeenCalledWith(2);
144+
expect(mockConsoleError).toHaveBeenCalledTimes(2);
145+
});
146+
147+
it("--workers exits on non-integer values", async () => {
148+
const args = [
149+
"node",
150+
"/path/to/node_modules/swc-cli/bin/swc.js",
151+
"--workers",
152+
"1.5",
153+
"src",
154+
];
155+
await parserArgs(args);
156+
expect(mockExit).toHaveBeenCalledWith(2);
157+
expect(mockConsoleError).toHaveBeenCalledTimes(2);
158+
});
133159
});
134160

135161
describe("--source-maps", () => {

src/swc/compile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export async function outputResult(
6262
} else {
6363
await Promise.all([
6464
writeFile(destFile, sourceCode, { mode }),
65-
writeFile(sourceMapPath, sourceMap, { mode }),
65+
writeFile(sourceMapPath, sourceMap!, { mode }),
6666
]);
6767
}
6868
}

src/swc/dir.ts

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import slash from "slash";
21
import { existsSync, promises } from "fs";
3-
import { dirname, relative, join } from "path";
2+
import { dirname, resolve } from "path";
3+
import Piscina from "piscina";
44
import { CompileStatus } from "./constants";
55
import { CliOptions } from "./options";
6-
import { compile, exists } from "./util";
7-
import { outputResult } from "./compile";
6+
import { exists, getDest } from "./util";
7+
import handleCompile from "./dirWorker";
88
import {
99
globSources,
1010
isCompilableExtension,
@@ -26,53 +26,8 @@ declare module "fs" {
2626

2727
const { mkdir, rmdir, rm, copyFile, unlink } = promises;
2828

29-
const cwd = process.cwd();
3029
const recursive = { recursive: true };
3130

32-
/**
33-
* Removes the leading directory, including all parent relative paths
34-
*/
35-
function stripComponents(filename: string) {
36-
const components = filename.split("/").slice(1);
37-
if (!components.length) {
38-
return filename;
39-
}
40-
while (components[0] === "..") {
41-
components.shift();
42-
}
43-
return components.join("/");
44-
}
45-
46-
function getDest(filename: string, outDir: string, ext?: string) {
47-
const relativePath = slash(relative(cwd, filename));
48-
let base = stripComponents(relativePath);
49-
if (ext) {
50-
base = base.replace(/\.\w*$/, ext);
51-
}
52-
return join(outDir, base);
53-
}
54-
55-
async function handleCompile(
56-
filename: string,
57-
outDir: string,
58-
sync: boolean,
59-
swcOptions: Options
60-
) {
61-
const dest = getDest(filename, outDir, ".js");
62-
const sourceFileName = slash(relative(dirname(dest), filename));
63-
64-
const options = { ...swcOptions, sourceFileName };
65-
66-
const result = await compile(filename, options, sync, dest);
67-
68-
if (result) {
69-
await outputResult(result, filename, dest, options);
70-
return CompileStatus.Compiled;
71-
} else {
72-
return CompileStatus.Omitted;
73-
}
74-
}
75-
7631
async function handleCopy(filename: string, outDir: string) {
7732
const dest = getDest(filename, outDir);
7833
const dir = dirname(dest);
@@ -126,7 +81,12 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) {
12681
if (sync) {
12782
for (const filename of compilable) {
12883
try {
129-
const result = await handleCompile(filename, outDir, sync, swcOptions);
84+
const result = await handleCompile({
85+
filename,
86+
outDir,
87+
sync,
88+
swcOptions,
89+
});
13090
results.set(filename, result);
13191
} catch (err: any) {
13292
console.error(err.message);
@@ -143,10 +103,16 @@ async function initialCompilation(cliOptions: CliOptions, swcOptions: Options) {
143103
}
144104
}
145105
} else {
106+
const workers = new Piscina({
107+
filename: resolve(__dirname, "./dirWorker.js"),
108+
maxThreads: cliOptions.workers,
109+
concurrentTasksPerWorker: 2,
110+
});
111+
146112
await Promise.all([
147113
Promise.allSettled(
148-
compilable.map(file =>
149-
handleCompile(file, outDir, sync, swcOptions).catch(err => {
114+
compilable.map(filename =>
115+
workers.run({ filename, outDir, sync, swcOptions }).catch(err => {
150116
console.error(err.message);
151117
throw err;
152118
})
@@ -264,12 +230,12 @@ async function watchCompilation(cliOptions: CliOptions, swcOptions: Options) {
264230
if (isCompilableExtension(filename, extensions)) {
265231
try {
266232
const start = process.hrtime();
267-
const result = await handleCompile(
233+
const result = await handleCompile({
268234
filename,
269235
outDir,
270236
sync,
271-
swcOptions
272-
);
237+
swcOptions,
238+
});
273239
if (!quiet && result === CompileStatus.Compiled) {
274240
const end = process.hrtime(start);
275241
console.log(

src/swc/dirWorker.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import slash from "slash";
2+
import { dirname, relative } from "path";
3+
import { CompileStatus } from "./constants";
4+
import { compile, getDest } from "./util";
5+
import { outputResult } from "./compile";
6+
7+
import type { Options } from "@swc/core";
8+
9+
export default async function handleCompile(opts: {
10+
filename: string;
11+
outDir: string;
12+
sync: boolean;
13+
swcOptions: Options;
14+
}) {
15+
const dest = getDest(opts.filename, opts.outDir, ".js");
16+
const sourceFileName = slash(relative(dirname(dest), opts.filename));
17+
18+
const options = { ...opts.swcOptions, sourceFileName };
19+
20+
const result = await compile(opts.filename, options, opts.sync, dest);
21+
22+
if (result) {
23+
await outputResult(result, opts.filename, dest, options);
24+
return CompileStatus.Compiled;
25+
} else {
26+
return CompileStatus.Omitted;
27+
}
28+
}

src/swc/options.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ export const initProgram = () => {
102102
collect
103103
);
104104

105+
program.option(
106+
"--workers [number]",
107+
"The number of workers to use for parallel processing"
108+
);
109+
105110
program.option(
106111
"--log-watch-compilation",
107112
"Log a message when a watched file is successfully compiled",
@@ -157,6 +162,7 @@ export interface CliOptions {
157162
* Invoke swc using transformSync. It's useful for debugging.
158163
*/
159164
readonly sync: boolean;
165+
readonly workers: number | undefined;
160166
readonly sourceMapTarget?: string;
161167
readonly filename: string;
162168
readonly filenames: string[];
@@ -207,6 +213,16 @@ export default function parserArgs(args: string[]) {
207213
);
208214
}
209215

216+
let workers: number | undefined;
217+
if (opts.workers != null) {
218+
workers = parseFloat(opts.workers);
219+
if (!Number.isInteger(workers) || workers < 0) {
220+
errors.push(
221+
"--workers must be a positive integer (found " + opts.workers + ")"
222+
);
223+
}
224+
}
225+
210226
if (errors.length) {
211227
console.error("swc:");
212228
for (const error of errors) {
@@ -265,6 +281,7 @@ export default function parserArgs(args: string[]) {
265281
filename: opts.filename,
266282
filenames,
267283
sync: !!opts.sync,
284+
workers,
268285
sourceMapTarget: opts.sourceMapTarget,
269286
extensions: opts.extensions || DEFAULT_EXTENSIONS,
270287
watch: !!opts.watch,

src/swc/util.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as swc from "@swc/core";
22
import slash from "slash";
33
import { mkdirSync, writeFileSync, promises } from "fs";
4-
import { dirname, relative } from "path";
4+
import { dirname, join, relative } from "path";
55

66
export async function exists(path: string): Promise<boolean> {
77
let pathExists = true;
@@ -125,3 +125,28 @@ export function assertCompilationResult<T>(
125125
);
126126
}
127127
}
128+
129+
/**
130+
* Removes the leading directory, including all parent relative paths
131+
*/
132+
function stripComponents(filename: string) {
133+
const components = filename.split("/").slice(1);
134+
if (!components.length) {
135+
return filename;
136+
}
137+
while (components[0] === "..") {
138+
components.shift();
139+
}
140+
return components.join("/");
141+
}
142+
143+
const cwd = process.cwd();
144+
145+
export function getDest(filename: string, outDir: string, ext?: string) {
146+
const relativePath = slash(relative(cwd, filename));
147+
let base = stripComponents(relativePath);
148+
if (ext) {
149+
base = base.replace(/\.\w*$/, ext);
150+
}
151+
return join(outDir, base);
152+
}

yarn.lock

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -795,10 +795,12 @@
795795
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014"
796796
integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==
797797

798-
"@types/node@^12.19.16":
799-
version "12.20.55"
800-
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
801-
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
798+
"@types/node@^20.11.5":
799+
version "20.11.5"
800+
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#be10c622ca7fcaa3cf226cf80166abc31389d86e"
801+
integrity sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==
802+
dependencies:
803+
undici-types "~5.26.4"
802804

803805
"@types/prettier@^2.1.5":
804806
version "2.7.2"
@@ -2260,6 +2262,24 @@ natural-compare@^1.4.0:
22602262
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
22612263
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
22622264

2265+
nice-napi@^1.0.2:
2266+
version "1.0.2"
2267+
resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b"
2268+
integrity sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==
2269+
dependencies:
2270+
node-addon-api "^3.0.0"
2271+
node-gyp-build "^4.2.2"
2272+
2273+
node-addon-api@^3.0.0:
2274+
version "3.2.1"
2275+
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
2276+
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
2277+
2278+
node-gyp-build@^4.2.2:
2279+
version "4.8.0"
2280+
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd"
2281+
integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==
2282+
22632283
node-int64@^0.4.0:
22642284
version "0.4.0"
22652285
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -2411,6 +2431,13 @@ pirates@^4.0.4:
24112431
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
24122432
integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
24132433

2434+
piscina@^4.3.0:
2435+
version "4.3.0"
2436+
resolved "https://registry.yarnpkg.com/piscina/-/piscina-4.3.0.tgz#fd219f507d410c61dbfb9bd4155c1f19eddb8535"
2437+
integrity sha512-vTQszGZj78p0BHFNO/cSvpzPUYa4tLXRe30aIYyQjqRS3fK/kPqdxvkTfGXQlEpWOI+mOOkda0iEY6NaanLWJA==
2438+
optionalDependencies:
2439+
nice-napi "^1.0.2"
2440+
24142441
pkg-dir@^4.2.0:
24152442
version "4.2.0"
24162443
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
@@ -2828,6 +2855,11 @@ typescript@~4.3.2:
28282855
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
28292856
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
28302857

2858+
undici-types@~5.26.4:
2859+
version "5.26.5"
2860+
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
2861+
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
2862+
28312863
update-browserslist-db@^1.0.10:
28322864
version "1.0.10"
28332865
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"

0 commit comments

Comments
 (0)