import { detectPackageManager, joinPathFragments } from '@nrwl/devkit'; import { capitalize } from '@nrwl/devkit/src/utils/string-utils'; import { checkFilesExist, cleanupProject, getPackageManagerCommand, isNotWindows, killPorts, newProject, packageManagerLockFile, readFile, rmDist, runCLI, runCommand, runCommandUntil, tmpProjPath, uniq, updateFile, updateProjectConfig, } from '@nrwl/e2e/utils'; import * as http from 'http'; import { checkApp } from './utils'; describe('Next.js Applications', () => { let proj: string; let originalEnv: string; let packageManager; beforeAll(() => { proj = newProject(); packageManager = detectPackageManager(tmpProjPath()); }); afterAll(() => cleanupProject()); beforeEach(() => { originalEnv = process.env.NODE_ENV; }); afterEach(() => { process.env.NODE_ENV = originalEnv; }); it('should generate app + libs', async () => { const appName = uniq('app'); const nextLib = uniq('nextlib'); const jsLib = uniq('tslib'); runCLI(`generate @nrwl/next:app ${appName} --no-interactive --style=css`); runCLI(`generate @nrwl/next:lib ${nextLib} --no-interactive`); runCLI(`generate @nrwl/js:lib ${jsLib} --no-interactive`); // Create file in public that should be copied to dist updateFile(`apps/${appName}/public/a/b.txt`, `Hello World!`); // Additional assets that should be copied to dist const sharedLib = uniq('sharedLib'); updateProjectConfig(appName, (json) => { json.targets.build.options.assets = [ { glob: '**/*', input: `libs/${sharedLib}/src/assets`, output: 'shared/ui', }, ]; return json; }); updateFile(`libs/${sharedLib}/src/assets/hello.txt`, 'Hello World!'); // create a css file in node_modules so that it can be imported in a lib // to test that it works as expected updateFile( 'node_modules/@nrwl/next/test-styles.css', 'h1 { background-color: red; }' ); updateFile( `libs/${jsLib}/src/lib/${jsLib}.ts`, ` export function testFn(): string { return 'Hello Nx'; }; // testing whether async-await code in Node / Next.js api routes works as expected export async function testAsyncFn() { return await Promise.resolve('hell0'); } ` ); const mainPath = `apps/${appName}/pages/index.tsx`; const content = readFile(mainPath); updateFile( `apps/${appName}/pages/api/hello.ts`, ` import { testAsyncFn } from '@${proj}/${jsLib}'; export default async function handler(_, res) { const value = await testAsyncFn(); res.send(value); } ` ); updateFile( mainPath, ` import { testFn } from '@${proj}/${jsLib}'; /* eslint-disable */ import dynamic from 'next/dynamic'; const TestComponent = dynamic( () => import('@${proj}/${nextLib}').then(d => d.${capitalize( nextLib )}) ); ${content.replace( ``, `
{testFn()}
); }; export default Index; ` ); updateFile( `apps/${appName}/pages/api/hello.js`, ` export default (_req, res) => { res.status(200).send('Welcome'); }; ` ); // serve Next.js const p = await runCommandUntil( `run ${appName}:serve --port=${port}`, (output) => { return output.indexOf(`[ ready ] on http://localhost:${port}`) > -1; } ); const apiData = await getData(port, '/external-api/hello'); const pageData = await getData(port, '/'); expect(apiData).toContain(`Welcome`); expect(pageData).toContain(`test value from a file`); await killPorts(); }, 300_000); it('should support custom next.config.js and output it in dist', async () => { const appName = uniq('app'); runCLI(`generate @nrwl/next:app ${appName} --no-interactive --style=css`); updateFile( `apps/${appName}/next.config.js`, ` const { withNx } = require('@nrwl/next/plugins/with-nx'); const nextConfig = { nx: { svgr: false, }, webpack: (config, context) => { // Make sure SVGR plugin is disabled if nx.svgr === false (see above) const found = config.module.rules.find(r => { if (!r.test || !r.test.test('test.svg')) return false; if (!r.oneOf || !r.oneOf.use) return false; return r.oneOf.use.some(rr => /svgr/.test(rr.loader)); }); if (found) throw new Error('Found SVGR plugin'); console.log('NODE_ENV is', process.env.NODE_ENV); return config; } }; module.exports = withNx(nextConfig); ` ); // deleting `NODE_ENV` value, so that it's `undefined`, and not `"test"` // by the time it reaches the build executor. // this simulates existing behaviour of running a next.js build executor via Nx delete process.env.NODE_ENV; const result = runCLI(`build ${appName}`); checkFilesExist(`dist/apps/${appName}/next.config.js`); expect(result).toContain('NODE_ENV is production'); updateFile( `apps/${appName}/next.config.js`, ` const { withNx } = require('@nrwl/next/plugins/with-nx'); // Not including "nx" entry should still work. const nextConfig = {}; module.exports = withNx(nextConfig); ` ); rmDist(); runCLI(`build ${appName}`); checkFilesExist(`dist/apps/${appName}/next.config.js`); }, 300_000); it('should support --js flag', async () => { const appName = uniq('app'); runCLI(`generate @nrwl/next:app ${appName} --no-interactive --js`); checkFilesExist(`apps/${appName}/pages/index.js`); await checkApp(appName, { checkUnitTest: true, checkLint: true, checkE2E: false, checkExport: false, }); // Consume a JS lib const libName = uniq('lib'); runCLI( `generate @nrwl/next:lib ${libName} --no-interactive --style=none --js` ); const mainPath = `apps/${appName}/pages/index.js`; updateFile( mainPath, `import '@${proj}/${libName}';\n` + readFile(mainPath) ); // Update lib to use css modules updateFile( `libs/${libName}/src/lib/${libName}.js`, ` import styles from './style.module.css'; export function Test() { return