Skip to content

Commit

Permalink
fix(js): ensure js libraries' build produce esm output in ts solution…
Browse files Browse the repository at this point in the history
… setup (#29546)

- Update js libraries for bundlers `esbuild`, `swc`, and `tsc` to
produce ESM output when using the TS solution setup.
- Fix `esbuild` and `swc` executors so they generate declaration files
even when skipping type-checking.
- Add `cjs` and `cts` to the `ignoredFiles` pattern for the rollup
config file in the eslint config.
- Ensure running an install after a js library is generated when using
the TS solution setup.

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
  • Loading branch information
leosvelperez authored Jan 8, 2025
1 parent 6b8ba97 commit 0334dad
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 29 deletions.
183 changes: 183 additions & 0 deletions e2e/js/src/js-ts-solution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import {
cleanupProject,
getPackageManagerCommand,
getSelectedPackageManager,
newProject,
runCLI,
runCommand,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';

describe('JS - TS solution setup', () => {
beforeAll(() => {
newProject({
packages: ['@nx/js'],
preset: 'ts',
});
});

afterAll(() => {
cleanupProject();
});

it('should generate libraries with different bundlers and link them successfully', () => {
const esbuildParentLib = uniq('esbuild-parent-lib');
const esbuildChildLib = uniq('esbuild-child-lib');
const rollupParentLib = uniq('rollup-parent-lib');
const rollupChildLib = uniq('rollup-child-lib');
const swcParentLib = uniq('swc-parent-lib');
const swcChildLib = uniq('swc-child-lib');
const tscParentLib = uniq('tsc-parent-lib');
const tscChildLib = uniq('tsc-child-lib');
const viteParentLib = uniq('vite-parent-lib');
const viteChildLib = uniq('vite-child-lib');

runCLI(
`generate @nx/js:lib packages/${esbuildParentLib} --bundler=esbuild --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${esbuildChildLib} --bundler=esbuild --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${rollupParentLib} --bundler=rollup --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${rollupChildLib} --bundler=rollup --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${swcParentLib} --bundler=swc --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${swcChildLib} --bundler=swc --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${tscParentLib} --bundler=tsc --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${tscChildLib} --bundler=tsc --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${viteParentLib} --bundler=vite --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/js:lib packages/${viteChildLib} --bundler=vite --linter=eslint --unitTestRunner=jest`
);

// add deps, each parent lib imports all child libs
const addImports = (parentLib: string) => {
updateFile(
`packages/${parentLib}/src/index.ts`,
(content) => `export * from '@proj/${esbuildChildLib}';
export * from '@proj/${rollupChildLib}';
export * from '@proj/${swcChildLib}';
export * from '@proj/${tscChildLib}';
export * from '@proj/${viteChildLib}';
${content}`
);
};

addImports(esbuildParentLib);
addImports(rollupParentLib);
addImports(swcParentLib);
addImports(tscParentLib);
addImports(viteParentLib);

const pm = getSelectedPackageManager();
if (pm === 'pnpm') {
// for pnpm we need to add the local packages as dependencies to each consumer package.json
const addDeps = (parentLib: string) => {
updateJson(`packages/${parentLib}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${esbuildChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${rollupChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${swcChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${tscChildLib}`] = 'workspace:*';
json.dependencies[`@proj/${viteChildLib}`] = 'workspace:*';
return json;
});
};

addDeps(esbuildParentLib);
addDeps(rollupParentLib);
addDeps(swcParentLib);
addDeps(tscParentLib);
addDeps(viteParentLib);

const pmc = getPackageManagerCommand({ packageManager: pm });
runCommand(pmc.install);
}

// sync to ensure the TS project references are updated
runCLI(`sync`);

// check build
expect(runCLI(`build ${esbuildParentLib}`)).toContain(
`Successfully ran target build for project ${esbuildParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${rollupParentLib}`)).toContain(
`Successfully ran target build for project ${rollupParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${swcParentLib}`)).toContain(
`Successfully ran target build for project ${swcParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${tscParentLib}`)).toContain(
`Successfully ran target build for project ${tscParentLib} and 5 tasks it depends on`
);
expect(runCLI(`build ${viteParentLib}`)).toContain(
`Successfully ran target build for project ${viteParentLib} and 5 tasks it depends on`
);

// check typecheck
expect(runCLI(`typecheck ${esbuildParentLib}`)).toContain(
`Successfully ran target typecheck for project ${esbuildParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${rollupParentLib}`)).toContain(
`Successfully ran target typecheck for project ${rollupParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${swcParentLib}`)).toContain(
`Successfully ran target typecheck for project ${swcParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${tscParentLib}`)).toContain(
`Successfully ran target typecheck for project ${tscParentLib} and 5 tasks it depends on`
);
expect(runCLI(`typecheck ${viteParentLib}`)).toContain(
`Successfully ran target typecheck for project ${viteParentLib} and 5 tasks it depends on`
);

// check lint
expect(runCLI(`lint ${esbuildParentLib}`)).toContain(
`Successfully ran target lint for project ${esbuildParentLib}`
);
expect(runCLI(`lint ${rollupParentLib}`)).toContain(
`Successfully ran target lint for project ${rollupParentLib}`
);
expect(runCLI(`lint ${swcParentLib}`)).toContain(
`Successfully ran target lint for project ${swcParentLib}`
);
expect(runCLI(`lint ${tscParentLib}`)).toContain(
`Successfully ran target lint for project ${tscParentLib}`
);
expect(runCLI(`lint ${viteParentLib}`)).toContain(
`Successfully ran target lint for project ${viteParentLib}`
);

// check test
expect(runCLI(`test ${esbuildParentLib}`)).toContain(
`Successfully ran target test for project ${esbuildParentLib}`
);
expect(runCLI(`test ${rollupParentLib}`)).toContain(
`Successfully ran target test for project ${rollupParentLib}`
);
expect(runCLI(`test ${swcParentLib}`)).toContain(
`Successfully ran target test for project ${swcParentLib}`
);
expect(runCLI(`test ${tscParentLib}`)).toContain(
`Successfully ran target test for project ${tscParentLib}`
);
expect(runCLI(`test ${viteParentLib}`)).toContain(
`Successfully ran target test for project ${viteParentLib}`
);
}, 300_000);
});
98 changes: 98 additions & 0 deletions e2e/vite/src/vite-ts-solution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { names } from '@nx/devkit';
import {
cleanupProject,
getPackageManagerCommand,
getSelectedPackageManager,
newProject,
runCLI,
runCommand,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';

describe('Vite - TS solution setup', () => {
beforeAll(() => {
newProject({
packages: ['@nx/react', '@nx/js'],
preset: 'ts',
});
});

afterAll(() => {
cleanupProject();
});

it('should generate app and consume libraries with different bundlers', () => {
const reactApp = uniq('react-app');
const esbuildLib = uniq('esbuild-lib');
const rollupLib = uniq('rollup-lib');
const swcLib = uniq('swc-lib');
const tscLib = uniq('tsc-lib');
const viteLib = uniq('vite-lib');
const noBundlerLib = uniq('no-bundler-lib');

runCLI(`generate @nx/react:app apps/${reactApp} --bundler=vite`);
runCLI(`generate @nx/js:lib packages/${esbuildLib} --bundler=esbuild`);
runCLI(`generate @nx/js:lib packages/${rollupLib} --bundler=rollup`);
runCLI(`generate @nx/js:lib packages/${swcLib} --bundler=swc`);
runCLI(`generate @nx/js:lib packages/${tscLib} --bundler=tsc`);
runCLI(`generate @nx/js:lib packages/${viteLib} --bundler=vite`);
runCLI(`generate @nx/js:lib packages/${noBundlerLib} --bundler=none`);

// import all libs from the app
updateFile(
`apps/${reactApp}/src/app/app.tsx`,
(content) => `import { ${
names(esbuildLib).propertyName
} } from '@proj/${esbuildLib}';
import { ${names(rollupLib).propertyName} } from '@proj/${rollupLib}';
import { ${names(swcLib).propertyName} } from '@proj/${swcLib}';
import { ${names(tscLib).propertyName} } from '@proj/${tscLib}';
import { ${names(viteLib).propertyName} } from '@proj/${viteLib}';
import { ${names(noBundlerLib).propertyName} } from '@proj/${noBundlerLib}';
console.log(
${names(esbuildLib).propertyName}(),
${names(rollupLib).propertyName}(),
${names(swcLib).propertyName}(),
${names(tscLib).propertyName}(),
${names(viteLib).propertyName}(),
${names(noBundlerLib).propertyName}()
);
${content}`
);

const pm = getSelectedPackageManager();
if (pm === 'pnpm') {
// for pnpm we need to add the local packages as dependencies to each consumer package.json
updateJson(`apps/${reactApp}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${esbuildLib}`] = 'workspace:*';
json.dependencies[`@proj/${rollupLib}`] = 'workspace:*';
json.dependencies[`@proj/${swcLib}`] = 'workspace:*';
json.dependencies[`@proj/${tscLib}`] = 'workspace:*';
json.dependencies[`@proj/${viteLib}`] = 'workspace:*';
json.dependencies[`@proj/${noBundlerLib}`] = 'workspace:*';
return json;
});

const pmc = getPackageManagerCommand({ packageManager: pm });
runCommand(pmc.install);
}

// sync to ensure the TS project references are updated
runCLI(`sync`);

// check build
expect(runCLI(`build ${reactApp}`)).toContain(
`Successfully ran target build for project ${reactApp} and 5 tasks it depends on`
);

// check typecheck
expect(runCLI(`typecheck ${reactApp}`)).toContain(
`Successfully ran target typecheck for project ${reactApp} and 6 tasks it depends on`
);
}, 300_000);
});
10 changes: 7 additions & 3 deletions packages/esbuild/src/executors/esbuild/esbuild.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ export async function* esbuildExecutor(
setup(build: esbuild.PluginBuild) {
build.onEnd(async (result: esbuild.BuildResult) => {
if (
!options.skipTypeCheck &&
!options.isTsSolutionSetup
!options.skipTypeCheck ||
options.isTsSolutionSetup
) {
const { errors } = await runTypeCheck(
options,
Expand Down Expand Up @@ -183,7 +183,7 @@ export async function* esbuildExecutor(
);
} else {
// Run type-checks first and bail if they don't pass.
if (!options.skipTypeCheck && !options.isTsSolutionSetup) {
if (!options.skipTypeCheck || options.isTsSolutionSetup) {
const { errors } = await runTypeCheck(options, context);
if (errors.length > 0) {
yield { success: false };
Expand Down Expand Up @@ -245,6 +245,10 @@ function getTypeCheckOptions(
typeCheckOptions.cacheDir = cacheDir;
}

if (options.isTsSolutionSetup && options.skipTypeCheck) {
typeCheckOptions.ignoreDiagnostics = true;
}

return typeCheckOptions;
}

Expand Down
4 changes: 1 addition & 3 deletions packages/js/src/executors/swc/swc.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ function normalizeOptions(

const outputPath = join(root, options.outputPath);

if (options.skipTypeCheck == null && !isTsSolutionSetup) {
options.skipTypeCheck = false;
}
options.skipTypeCheck ??= !isTsSolutionSetup;

if (options.watch == null) {
options.watch = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { <%= propertyName %> } from './<%= fileName %>';
import { <%= propertyName %> } from './<%= fileNameImport %>';

describe('<%= propertyName %>', () => {
it('should work', () => {
Expand Down
Loading

0 comments on commit 0334dad

Please sign in to comment.