Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/nx-plugin/src/smithy/ts/api/generator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,19 @@ describe('tsSmithyApiGenerator', () => {
expect(bundleTarget.options.command).toEqual(
'rolldown -c rolldown.config.ts',
);

const rolldownConfig = tree.read(
'test-api/backend/rolldown.config.ts',
'utf-8',
);

expect(rolldownConfig).toContain('defineConfig');
expect(rolldownConfig).toContain('src/handler.ts');
expect(rolldownConfig).toContain(
'../../dist/test-api/backend/bundle/index.js',
);
// AWS SDK is provided by lambda runtime
expect(rolldownConfig).toContain('external: [/@aws-sdk\\/.*/]');
});

it('should add generator metadata to backend project configuration', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/nx-plugin/src/smithy/ts/api/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const tsSmithyApiGenerator = async (
// Add bundle target using rolldown
addTypeScriptBundleTarget(tree, backendProjectConfig, {
targetFilePath: 'src/handler.ts',
external: [/@aws-sdk\/.*/], // lambda runtime provides aws sdk
});

const cmd = new FsCommands(tree);
Expand Down
3 changes: 3 additions & 0 deletions packages/nx-plugin/src/trpc/backend/generator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ describe('trpc backend generator', () => {
expect(rolldownConfig).toContain(
'../../dist/apps/test-api/bundle/index.js',
);

// AWS SDK is provided by lambda runtime
expect(rolldownConfig).toContain('external: [/@aws-sdk\\/.*/]');
});

it('should add rolldown dependency to package.json', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/nx-plugin/src/trpc/backend/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export async function tsTrpcApiGenerator(

addTypeScriptBundleTarget(tree, projectConfig, {
targetFilePath: 'src/router.ts',
external: [/@aws-sdk\/.*/], // lambda runtime provides aws sdk
});

addDependencyToTargetIfNotPresent(projectConfig, 'build', 'bundle');
Expand Down
2 changes: 1 addition & 1 deletion packages/nx-plugin/src/ts/lambda-function/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export const tsLambdaFunctionGenerator = async (
addTypeScriptBundleTarget(tree, projectConfig, {
targetFilePath: functionPathFromProjectRoot,
bundleOutputDir,
external: [/@aws-sdk\/.*/],
external: [/@aws-sdk\/.*/], // lambda runtime provides aws sdk
});

projectConfig.targets = sortObjectKeys(projectConfig.targets);
Expand Down
2 changes: 2 additions & 0 deletions packages/nx-plugin/src/ts/mcp-server/generator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,8 @@ describe('ts#mcp-server generator', () => {
expect(rolldownConfig).toContain(
'../../dist/apps/test-project/bundle/mcp/test-project-mcp-server/index.js',
);
// Tree shaking should be disabled for the AWS SDK
expect(rolldownConfig).toContain('disableTreeShake([/@aws-sdk\\/.*/])');
});

it('should ensure Dockerfile COPY path matches bundle output path', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`bundle utilities > addTypeScriptBundleTarget > should add disableTreeShake function and plugins when AWS SDK is not external 1`] = `
"import { defineConfig } from 'rolldown';

// Disables tree-shaking for the given module patterns
const disableTreeShake = (patterns: RegExp[]) => ({
name: 'disable-treeshake',
transform: (code, id) => {
if (patterns.some(p => p.test(id))) {
return { code, map: null, moduleSideEffects: 'no-treeshake' };
}
return null;
},
});

export default defineConfig([{
input: 'src/index.ts',
output: {
file: '../../dist/apps/test-project/bundle/index.js',
format: 'cjs',
inlineDynamicImports: true
},
platform: 'node',
plugins: [disableTreeShake([/@aws-sdk\\/.*/])]
}]);
"
`;

exports[`bundle utilities > addTypeScriptBundleTarget > should not add disableTreeShake or plugins when AWS SDK is external as regex 1`] = `
"import { defineConfig } from 'rolldown';

export default defineConfig([{
input: 'src/index.ts',
output: {
file: '../../dist/apps/test-project/bundle/index.js',
format: 'cjs',
inlineDynamicImports: true
},
platform: 'node',
external: [/@aws-sdk\\/.*/]
}]);
"
`;
154 changes: 154 additions & 0 deletions packages/nx-plugin/src/utils/bundle/bundle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,160 @@ export default defineConfig([
expect(configContent).toContain('src/index.ts');
expect(configContent).toContain('src/handler.ts');
});

it('should configure platform with default value of node', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

expect(configContent).toContain("platform: 'node'");
});

it('should configure platform when explicitly set to node', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
platform: 'node',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

expect(configContent).toContain("platform: 'node'");
});

it('should configure platform when set to browser', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
platform: 'browser',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

expect(configContent).toContain("platform: 'browser'");
});

it('should configure platform when set to neutral', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
platform: 'neutral',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

expect(configContent).toContain("platform: 'neutral'");
});

it('should add disableTreeShake function and plugins when AWS SDK is not external', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

// Should include the disableTreeShake function
expect(configContent).toContain('const disableTreeShake');
expect(configContent).toContain("name: 'disable-treeshake'");
expect(configContent).toContain("moduleSideEffects: 'no-treeshake'");

// Should include plugins configuration
expect(configContent).toContain(
'plugins: [disableTreeShake([/@aws-sdk\\/.*/])]',
);

expect(
tree.read('apps/test-project/rolldown.config.ts', 'utf-8'),
).toMatchSnapshot();
});

it('should not add disableTreeShake or plugins when AWS SDK is external as regex', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
external: [/@aws-sdk\/.*/],
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

// Should NOT include the disableTreeShake function
expect(configContent).not.toContain('const disableTreeShake');
expect(configContent).not.toContain("name: 'disable-treeshake'");

// Should NOT include plugins configuration
expect(configContent).not.toContain('plugins:');

expect(
tree.read('apps/test-project/rolldown.config.ts', 'utf-8'),
).toMatchSnapshot();
});

it('should not add disableTreeShake or plugins when AWS SDK is external with other dependencies', () => {
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
external: ['lodash', /@aws-sdk\/.*/, 'react'],
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

// Should NOT include the disableTreeShake function
expect(configContent).not.toContain('const disableTreeShake');

// Should NOT include plugins configuration
expect(configContent).not.toContain('plugins:');

// Should still have external configuration
expect(configContent).toContain('external:');
});

it('should add disableTreeShake only once when called multiple times', () => {
// First call
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/index.ts',
});

// Second call with different target
addTypeScriptBundleTarget(tree, project, {
targetFilePath: 'src/handler.ts',
bundleOutputDir: 'handler',
});

const configContent = tree.read(
'apps/test-project/rolldown.config.ts',
'utf-8',
);

// Count occurrences of disableTreeShake function definition
const disableTreeShakeOccurrences = (
configContent.match(/const disableTreeShake = /g) || []
).length;
expect(disableTreeShakeOccurrences).toBe(1);

// Both configs should have plugins
const pluginsOccurrences = (
configContent.match(/plugins: \[disableTreeShake/g) || []
).length;
expect(pluginsOccurrences).toBe(2);
});
});

describe('addPythonBundleTarget', () => {
Expand Down
71 changes: 71 additions & 0 deletions packages/nx-plugin/src/utils/bundle/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ export interface AddTypeScriptBundleTargetOptions {
* Modules to omit from the bundle and treat as external
*/
external?: (string | RegExp)[];

/**
* Target platform for the bundle
* @default 'node'
*/
platform?: 'node' | 'browser' | 'neutral';
}

/**
Expand All @@ -139,6 +145,14 @@ export const addTypeScriptBundleTarget = (
) => {
project.targets ??= {};

// Check if AWS SDK is external
const awsSdkRegex = /@aws-sdk\/.*/;
const isAwsSdkExternal = opts.external?.some((ext) =>
ext instanceof RegExp
? ext.source === awsSdkRegex.source
: ext === awsSdkRegex.source,
);

// Generate empty rolldown config if it doesn't exist
generateFiles(
tree,
Expand Down Expand Up @@ -173,6 +187,39 @@ export const addTypeScriptBundleTarget = (
const rolldownConfigArraySelector =
'CallExpression:has(Identifier[name="defineConfig"]) > ArrayLiteralExpression';

// Add disableTreeShake function if AWS SDK is not external and it doesn't exist
// This is due to an issue with rolldown bundling for the SDK, and can likely be removed when resolved
// https://github.com/rolldown/rolldown/issues/6513
if (!isAwsSdkExternal) {
const disableTreeShakeSelector =
'VariableDeclaration:has(Identifier[name="disableTreeShake"])';
if (
query(tree, rolldownConfigPath, disableTreeShakeSelector).length === 0
) {
const content = tree.read(rolldownConfigPath, 'utf-8');
const disableTreeShakeFunction = `// Disables tree-shaking for the given module patterns
const disableTreeShake = (patterns: RegExp[]) => ({
name: 'disable-treeshake',
transform: (code, id) => {
if (patterns.some(p => p.test(id))) {
return { code, map: null, moduleSideEffects: 'no-treeshake' };
}
return null;
},
});

`;
const defineConfigIndex = content.indexOf('export default defineConfig');
if (defineConfigIndex !== -1) {
const newContent =
content.slice(0, defineConfigIndex) +
disableTreeShakeFunction +
content.slice(defineConfigIndex);
tree.write(rolldownConfigPath, newContent);
}
}
}

// Check whether we already have a config entry with input set to targetFilePath
if (
query(
Expand Down Expand Up @@ -225,6 +272,10 @@ export const addTypeScriptBundleTarget = (
true,
),
),
factory.createPropertyAssignment(
factory.createIdentifier('platform'),
factory.createStringLiteral(opts.platform ?? 'node', true),
),
...(opts.external
? [
factory.createPropertyAssignment(
Expand All @@ -241,6 +292,26 @@ export const addTypeScriptBundleTarget = (
),
]
: []),
...(!isAwsSdkExternal
? [
factory.createPropertyAssignment(
factory.createIdentifier('plugins'),
factory.createArrayLiteralExpression([
factory.createCallExpression(
factory.createIdentifier('disableTreeShake'),
undefined,
[
factory.createArrayLiteralExpression([
factory.createRegularExpressionLiteral(
`/${awsSdkRegex.source}/`,
),
]),
],
),
]),
),
]
: []),
],
true,
),
Expand Down
2 changes: 1 addition & 1 deletion packages/nx-plugin/src/utils/test/ts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class TypeScriptVerifier {
encoding: 'utf-8',
stdio: 'pipe',
cwd: path.resolve(__dirname, '../../../../../'),
maxBuffer: 50 * 1024 * 1024, // 50 MB
maxBuffer: 1024 * 1024 * 1024, // 1 GB
}),
);

Expand Down
Loading