Skip to content

Commit

Permalink
feat(bundling): extract rollup plugins into withNx function for use w…
Browse files Browse the repository at this point in the history
…ith run-commands (#26168)

This PR adds `withNx` function to `@nx/rollup/with-nx` so it can be used
in `rollup.config.js` to replicate what `@nx/rollup:rollup` executor
does without needing to use the executor.

e.g. 

```js
// rollup.config.js
const { withNx } = require("@nx/rollup/with-nx");

module.exports = withNx(
  {
    main: "./src/index.ts",
    outputPath: "./dist",
    tsConfig: "./tsconfig.lib.json",
    compiler: "babel",
    external: ["react", "react-dom", "react/jsx-runtime"],
    format: ["esm"],
    assets: [{ input: ".", output: ".", glob: "README.md" }],
  },
  {
    // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options
    // e.g.
    // output: { sourcemap: true },
  }
);
```


## Notes

1. Existing `@nx/rollup:rollup` continues to encapsulate rollup options
and will not support an isolated mode.
2. Newly created JS and React libs with `--bundler=rollup` will use the
new `withNx` function and explicit `rollup.config.js`.
3. If `NX_ADD_PLUGINS=false` or `useInferencePlugins: false` is set,
then new projects will continue to use the `@nx/rollup:rollup` executor.
  • Loading branch information
jaysoo authored May 31, 2024
1 parent c05e4ac commit 4e49d52
Show file tree
Hide file tree
Showing 34 changed files with 1,668 additions and 750 deletions.
202 changes: 202 additions & 0 deletions e2e/react/src/react-package-legacy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getSize,
killPorts,
newProject,
readFile,
readJson,
rmDist,
runCLI,
runCLIAsync,
tmpProjPath,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { names } from '@nx/devkit';
import { join } from 'path';

describe('Build React libraries and apps', () => {
/**
* Graph:
*
* childLib
* /
* app => parentLib =>
* \
* childLib2
*
*/
let app: string;
let parentLib: string;
let childLib: string;
let childLib2: string;

let proj: string;

beforeEach(async () => {
process.env.NX_ADD_PLUGINS = 'false';
app = uniq('app');
parentLib = uniq('parentlib');
childLib = uniq('childlib');
childLib2 = uniq('childlib2');

proj = newProject({ packages: ['@nx/react'] });

// create dependencies by importing
const createDep = (parent, children: string[]) => {
updateFile(
`libs/${parent}/src/index.ts`,
`
export * from './lib/${parent}';
${children
.map(
(entry) =>
`import { ${
names(entry).className
} } from '@${proj}/${entry}'; console.log(${
names(entry).className
});`
)
.join('\n')}
`
);
};

runCLI(`generate @nx/react:app ${app} `);
updateJson('nx.json', (json) => ({
...json,
generators: {
...json.generators,
'@nx/react': {
library: {
unitTestRunner: 'none',
},
},
},
}));
// generate buildable libs
runCLI(
`generate @nx/react:library ${parentLib} --bundler=rollup --importPath=@${proj}/${parentLib} --no-interactive --unitTestRunner=jest --skipFormat`
);
runCLI(
`generate @nx/react:library ${childLib} --bundler=rollup --importPath=@${proj}/${childLib} --no-interactive --unitTestRunner=jest --skipFormat`
);
runCLI(
`generate @nx/react:library ${childLib2} --bundler=rollup --importPath=@${proj}/${childLib2} --no-interactive --unitTestRunner=jest --skipFormat`
);

createDep(parentLib, [childLib, childLib2]);

updateFile(
`apps/${app}/src/main.tsx`,
`
import {${names(parentLib).className}} from "@${proj}/${parentLib}";
console.log(${names(parentLib).className});
`
);

// Add assets to child lib
updateJson(join('libs', childLib, 'project.json'), (json) => {
json.targets.build.options.assets = [`libs/${childLib}/src/assets`];
return json;
});
updateFile(`libs/${childLib}/src/assets/hello.txt`, 'Hello World!');
});

afterEach(() => {
killPorts();
cleanupProject();
delete process.env.NX_ADD_PLUGINS;
});

describe('Buildable libraries', () => {
it('should build libraries with and without dependencies', () => {
/*
* 1. Without dependencies
*/
runCLI(`build ${childLib}`);
runCLI(`build ${childLib2}`);

checkFilesExist(`dist/libs/${childLib}/index.esm.js`);

checkFilesExist(`dist/libs/${childLib2}/index.esm.js`);

checkFilesExist(`dist/libs/${childLib}/assets/hello.txt`);
checkFilesExist(`dist/libs/${childLib2}/README.md`);

/*
* 2. With dependencies without existing dist
*/
rmDist();

runCLI(`build ${parentLib} --skip-nx-cache`);

checkFilesExist(`dist/libs/${parentLib}/index.esm.js`);
checkFilesExist(`dist/libs/${childLib}/index.esm.js`);
checkFilesExist(`dist/libs/${childLib2}/index.esm.js`);

expect(readFile(`dist/libs/${childLib}/index.esm.js`)).not.toContain(
'react/jsx-dev-runtime'
);
expect(readFile(`dist/libs/${childLib}/index.esm.js`)).toContain(
'react/jsx-runtime'
);
});

it('should support --format option', () => {
updateFile(
`libs/${childLib}/src/index.ts`,
(s) => `${s}
export async function f() { return 'a'; }
export async function g() { return 'b'; }
export async function h() { return 'c'; }
`
);

runCLI(`build ${childLib} --format cjs,esm`);

checkFilesExist(`dist/libs/${childLib}/index.cjs.js`);
checkFilesExist(`dist/libs/${childLib}/index.esm.js`);

const cjsPackageSize = getSize(
tmpProjPath(`dist/libs/${childLib}/index.cjs.js`)
);
const esmPackageSize = getSize(
tmpProjPath(`dist/libs/${childLib}/index.esm.js`)
);

// This is a loose requirement that ESM should be smaller than CJS output.
expect(esmPackageSize).toBeLessThanOrEqual(cjsPackageSize);
});

it('should build an app composed out of buildable libs', () => {
const buildFromSource = runCLI(
`build ${app} --buildLibsFromSource=false`
);
expect(buildFromSource).toContain('Successfully ran target build');
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
}, 1000000);

it('should not create a dist folder if there is an error', async () => {
const libName = uniq('lib');

runCLI(
`generate @nx/react:lib ${libName} --bundler=rollup --importPath=@${proj}/${libName} --no-interactive --unitTestRunner=jest`
);

const mainPath = `libs/${libName}/src/lib/${libName}.tsx`;
updateFile(mainPath, `${readFile(mainPath)}\n console.log(a);`); // should error - "a" will be undefined

await expect(runCLIAsync(`build ${libName}`)).rejects.toThrow(
/Bundle failed/
);
expect(() => {
checkFilesExist(`dist/libs/${libName}/package.json`);
}).toThrow();
}, 250000);
});
});
62 changes: 17 additions & 45 deletions e2e/react/src/react-package.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getSize,
killPorts,
newProject,
readFile,
readJson,
rmDist,
runCLI,
runCLIAsync,
tmpProjPath,
uniq,
updateFile,
updateJson,
Expand All @@ -37,7 +33,6 @@ describe('Build React libraries and apps', () => {
let proj: string;

beforeEach(async () => {
process.env.NX_ADD_PLUGINS = 'false';
app = uniq('app');
parentLib = uniq('parentlib');
childLib = uniq('childlib');
Expand Down Expand Up @@ -100,17 +95,28 @@ describe('Build React libraries and apps', () => {
);

// Add assets to child lib
updateJson(join('libs', childLib, 'project.json'), (json) => {
json.targets.build.options.assets = [`libs/${childLib}/src/assets`];
return json;
});
updateFile(
join('libs', childLib, 'rollup.config.js'),
`const { withNx } = require('@nx/rollup/with-nx');
module.exports = withNx(
{
main: './src/index.ts',
outputPath: '../../dist/libs/${childLib}',
tsConfig: './tsconfig.lib.json',
compiler: 'babel',
external: ['react', 'react-dom', 'react/jsx-runtime'],
format: ['esm'],
assets: ['./src/assets'],
}
);
`
);
updateFile(`libs/${childLib}/src/assets/hello.txt`, 'Hello World!');
});

afterEach(() => {
killPorts();
cleanupProject();
delete process.env.NX_ADD_PLUGINS;
});

describe('Buildable libraries', () => {
Expand Down Expand Up @@ -147,32 +153,6 @@ describe('Build React libraries and apps', () => {
);
});

it('should support --format option', () => {
updateFile(
`libs/${childLib}/src/index.ts`,
(s) => `${s}
export async function f() { return 'a'; }
export async function g() { return 'b'; }
export async function h() { return 'c'; }
`
);

runCLI(`build ${childLib} --format cjs,esm`);

checkFilesExist(`dist/libs/${childLib}/index.cjs.js`);
checkFilesExist(`dist/libs/${childLib}/index.esm.js`);

const cjsPackageSize = getSize(
tmpProjPath(`dist/libs/${childLib}/index.cjs.js`)
);
const esmPackageSize = getSize(
tmpProjPath(`dist/libs/${childLib}/index.esm.js`)
);

// This is a loose requirement that ESM should be smaller than CJS output.
expect(esmPackageSize).toBeLessThanOrEqual(cjsPackageSize);
});

it('should preserve the tsconfig target set by user', () => {
// Setup
const myLib = uniq('my-lib');
Expand Down Expand Up @@ -224,14 +204,6 @@ export async function h() { return 'c'; }
expect(content).toContain('function __generator(thisArg, body) {');
});

it('should build an app composed out of buildable libs', () => {
const buildFromSource = runCLI(
`build ${app} --buildLibsFromSource=false`
);
expect(buildFromSource).toContain('Successfully ran target build');
checkFilesDoNotExist(`apps/${app}/tsconfig/tsconfig.nx-tmp`);
}, 1000000);

it('should not create a dist folder if there is an error', async () => {
const libName = uniq('lib');

Expand All @@ -243,7 +215,7 @@ export async function h() { return 'c'; }
updateFile(mainPath, `${readFile(mainPath)}\n console.log(a);`); // should error - "a" will be undefined

await expect(runCLIAsync(`build ${libName}`)).rejects.toThrow(
/Bundle failed/
/Command failed/
);
expect(() => {
checkFilesExist(`dist/libs/${libName}/package.json`);
Expand Down
Loading

0 comments on commit 4e49d52

Please sign in to comment.