Skip to content

Commit 7642359

Browse files
authored
chore(clients): use rollup for dist-cjs (#7406)
* chore(clients): use rollup for dist-cjs * chore: more naming exceptions for inliner * chore: build rollup in multi-entry mode * chore: add known build alias * chore: clean only tsc files * chore: add rollup cache to gitignore
1 parent 0f28213 commit 7642359

File tree

4 files changed

+240
-61
lines changed

4 files changed

+240
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ workspace
5454

5555
.turbo
5656
turbo.dev.json
57+
.rollup.cache
5758
coverage
5859
dist-*
5960

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
"@fastify/formbody": "^7.4.0",
7474
"@microsoft/api-extractor": "7.52.7",
7575
"@mixer/parallel-prettier": "2.0.3",
76+
"@rollup/plugin-json": "^6.1.0",
77+
"@rollup/plugin-node-resolve": "^16.0.2",
78+
"@rollup/plugin-typescript": "^12.1.4",
7679
"@tsconfig/recommended": "1.0.1",
7780
"@types/fs-extra": "^8.0.1",
7881
"@types/jest": "29.5.11",

scripts/compilation/Inliner.js

Lines changed: 143 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ const fs = require("fs");
22
const path = require("path");
33
const { spawnProcess } = require("./../utils/spawn-process");
44
const walk = require("./../utils/walk");
5-
const esbuild = require("esbuild");
5+
const rollup = require("rollup");
6+
const { nodeResolve } = require("@rollup/plugin-node-resolve");
7+
const typescript = require("@rollup/plugin-typescript");
8+
const json = require("@rollup/plugin-json");
69

710
const root = path.join(__dirname, "..", "..");
811

@@ -143,6 +146,8 @@ module.exports = class Inliner {
143146
}
144147
}
145148

149+
this.variantExternals = [...new Set(this.variantExternals)];
150+
146151
return this;
147152
}
148153

@@ -155,30 +160,67 @@ module.exports = class Inliner {
155160
return this;
156161
}
157162

158-
this.variantExternalsForEsBuild = this.variantExternals.map(
159-
(variant) => "*/" + path.basename(variant).replace(/.js$/, "")
160-
);
163+
const variantExternalsForRollup = this.variantExternals.map((variant) => variant.replace(/.js$/, ""));
164+
165+
const entryPoint = path.join(root, this.subfolder, this.package, "src", "index.ts");
166+
167+
const inputOptions = (externals) => ({
168+
input: [entryPoint],
169+
plugins: [
170+
nodeResolve(),
171+
json(),
172+
typescript({
173+
compilerOptions: {
174+
importHelpers: true,
175+
noEmitHelpers: false,
176+
module: "esnext",
177+
target: "es2022",
178+
noCheck: true,
179+
removeComments: true,
180+
},
181+
}),
182+
],
183+
external: (id) => {
184+
const relative = !!id.match(/^\.?\.?\//);
185+
if (!relative) {
186+
this.verbose && console.log("EXTERN (pkg)", id);
187+
return true;
188+
}
161189

162-
const buildOptions = {
163-
platform: this.platform,
164-
target: ["node18"],
165-
bundle: true,
166-
format: "cjs",
167-
mainFields: ["main"],
168-
allowOverwrite: true,
169-
entryPoints: [path.join(root, this.subfolder, this.package, "src", "index.ts")],
170-
supported: {
171-
"dynamic-import": false,
190+
const local = id.includes(`/packages/`) && id.includes(`/dist-es/`);
191+
if (local) {
192+
this.verbose && console.log("EXTERN (local)", id);
193+
return true;
194+
}
195+
196+
if (id === entryPoint) {
197+
this.verbose && console.log("INTERN (entry point)", id);
198+
return false;
199+
}
200+
201+
for (const file of externals) {
202+
const idWithoutExtension = id.replace(/\.ts$/, "");
203+
if (idWithoutExtension.endsWith(path.basename(file))) {
204+
this.verbose && console.log("EXTERN (variant)", id);
205+
return true;
206+
}
207+
}
208+
209+
this.verbose && console.log("INTERN (invariant)", id);
210+
return false;
172211
},
173-
outfile: this.outfile,
174-
keepNames: true,
175-
packages: "external",
176-
external: ["@smithy/*", "@aws-sdk/*", "node_modules/*", ...this.variantExternalsForEsBuild],
212+
});
213+
214+
const outputOptions = {
215+
dir: path.dirname(this.outfile),
216+
format: "cjs",
217+
exports: "named",
218+
preserveModules: false,
177219
};
178220

179-
if (!this.hasSubmodules) {
180-
await esbuild.build(buildOptions);
181-
}
221+
const bundle = await rollup.rollup(inputOptions(variantExternalsForRollup));
222+
await bundle.write(outputOptions);
223+
await bundle.close();
182224

183225
if (this.hasSubmodules) {
184226
const submodules = fs.readdirSync(path.join(root, this.subfolder, this.package, "src", "submodules"));
@@ -206,19 +248,41 @@ module.exports = class Inliner {
206248
}
207249
}
208250

209-
await esbuild.build({
210-
...buildOptions,
211-
entryPoints: [path.join(root, this.subfolder, this.package, "src", "submodules", submodule, "index.ts")],
212-
outfile: path.join(root, this.subfolder, this.package, "dist-cjs", "submodules", submodule, "index.js"),
213-
external: [
214-
"@smithy/*",
215-
"@aws-sdk/*",
216-
"node_modules/*",
217-
...this.variantExternals
218-
.filter((variant) => variant.includes(`submodules/${submodule}`))
219-
.map((variant) => "*/" + path.basename(variant).replace(/.js$/, "")),
220-
],
251+
// remove remaining empty directories.
252+
const submoduleFolder = path.join(root, this.subfolder, this.package, "dist-cjs", "submodules", submodule);
253+
function rmdirEmpty(dir) {
254+
for (const entry of fs.readdirSync(dir)) {
255+
const fullPath = path.join(dir, entry);
256+
if (fs.lstatSync(fullPath).isDirectory()) {
257+
if (fs.readdirSync(fullPath).length) {
258+
rmdirEmpty(fullPath);
259+
} else {
260+
fs.rmdirSync(fullPath);
261+
}
262+
}
263+
}
264+
}
265+
rmdirEmpty(submoduleFolder);
266+
267+
const submoduleVariants = variantExternalsForRollup.filter((external) =>
268+
external.includes(`submodules/${submodule}`)
269+
);
270+
271+
const submoduleOptions = inputOptions(submoduleVariants);
272+
273+
const submoduleBundle = await rollup.rollup({
274+
...submoduleOptions,
275+
input: path.join(root, this.subfolder, this.package, "src", "submodules", submodule, "index.ts"),
276+
});
277+
278+
await submoduleBundle.write({
279+
...outputOptions,
280+
dir: path.dirname(
281+
path.join(root, this.subfolder, this.package, "dist-cjs", "submodules", submodule, "index.js")
282+
),
221283
});
284+
285+
await submoduleBundle.close();
222286
}
223287
}
224288

@@ -271,7 +335,9 @@ module.exports = class Inliner {
271335
.join("/")) + "/index.js";
272336

273337
if (!this.reExportStubs) {
274-
fs.rmSync(file);
338+
if (fs.readFileSync(file, "utf-8").includes(`Object.defineProperty(exports, "__esModule", { value: true });`)) {
339+
fs.rmSync(file);
340+
}
275341
const files = fs.readdirSync(path.dirname(file));
276342
if (files.length === 0) {
277343
fs.rmdirSync(path.dirname(file));
@@ -463,33 +529,51 @@ module.exports = class Inliner {
463529
}
464530

465531
// check ESM compat.
466-
const tmpFileContents = this.canonicalExports
467-
.filter((sym) => !sym.includes(":"))
468-
.map((sym) => `import { ${sym} } from "${this.pkgJson.name}";`)
469-
.join("\n");
532+
const tmpFileContents =
533+
`import assert from "node:assert";
534+
535+
const namingExceptions = [
536+
"paginateOperation" // name for all paginators.
537+
];
538+
` +
539+
this.canonicalExports
540+
.filter((sym) => !sym.includes(":"))
541+
.map((sym) => {
542+
if (
543+
[
544+
"AWSSDKSigV4Signer", // deprecated alias for AwsSdkSigV4Signer
545+
"resolveAWSSDKSigV4Config", // deprecated alias for resolveAwsSdkSigV4Config
546+
"__Client", // base Client in SDK clients
547+
"$Command", // base Command in SDK clients
548+
"getDefaultClientConfiguration", // renamed to getDefaultExtensionConfiguration
549+
"generateIdempotencyToken", // sometimes called v4
550+
"defaultUserAgent", // renamed to createDefaultUserAgentProvider
551+
"getSigV4AuthPlugin", // legacy auth, getAwsAuthPlugin
552+
"NumberValueImpl", // name of NumberValue
553+
554+
"WorkSpacesThin", // alias of WorkSpacesThinClient
555+
556+
"HostResolver", // alias of NodeDnsLookupHostResolver
557+
"expectInt", // aliased to expectLong
558+
"handleFloat", // aliased to limitedParseDouble
559+
"limitedParseFloat", // aliased to limitedParseDouble
560+
"strictParseFloat", // aliased to strictParseDouble
561+
"strictParseInt", // aliased to strictParseLong
562+
].includes(sym)
563+
) {
564+
return `import { ${sym} } from "${this.pkgJson.name}";`;
565+
}
566+
return `import { ${sym} } from "${this.pkgJson.name}";
567+
if (typeof ${sym} === "function") {
568+
if (${sym}.name !== "${sym}" && !namingExceptions.includes(${sym}.name)) {
569+
throw new Error(${sym}.name + " does not equal expected ${sym}.")
570+
}
571+
}
572+
`;
573+
})
574+
.join("\n");
470575
fs.writeFileSync(path.join(__dirname, "tmp", this.package + ".mjs"), tmpFileContents, "utf-8");
471576
await spawnProcess("node", [path.join(__dirname, "tmp", this.package + ".mjs")]);
472-
473-
if (this.hasSubmodules) {
474-
const submodules = fs.readdirSync(path.join(root, this.subfolder, this.package, "src", "submodules"));
475-
for (const submodule of submodules) {
476-
const canonicalExports = Object.keys(
477-
require(path.join(root, this.subfolder, this.package, "dist-cjs", "submodules", submodule, "index.js"))
478-
);
479-
const tmpFileContents = canonicalExports
480-
.filter((sym) => !sym.includes(":"))
481-
.map((sym) => `import { ${sym} } from "${this.pkgJson.name}/${submodule}";`)
482-
.join("\n");
483-
const tmpFilePath = path.join(__dirname, "tmp", this.package + "_" + submodule + ".mjs");
484-
fs.writeFileSync(tmpFilePath, tmpFileContents, "utf-8");
485-
await spawnProcess("node", [tmpFilePath]);
486-
fs.rmSync(tmpFilePath);
487-
if (this.verbose) {
488-
console.log("ESM compatibility verified for submodule", submodule);
489-
}
490-
}
491-
}
492-
493577
if (this.verbose) {
494578
console.log("ESM compatibility verified.");
495579
}

0 commit comments

Comments
 (0)