fix(nextjs): add extension alias support for handling ESM libs (#31323)
<!-- 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 --> Currently, if you try to import a ESM lib after you generate a Next.js application it fails to build due to how the module exports `export * from './lib/lib8446520.js';`. This has been addressed with webpack and needs to be extended to Next.js. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> You should work out of the box and you should be able to import a lib defined like: `export * from './lib/lib8446520.js';.` ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> This is also related to our webpack and rspack packages. Changes have also been made to them to ensure consistency across bundlers. Fixes #30714
This commit is contained in:
parent
5c405fa72f
commit
ae89efb8d1
63
e2e/next/src/next-ts-solutions.test.ts
Normal file
63
e2e/next/src/next-ts-solutions.test.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
cleanupProject,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
newProject,
|
||||||
|
readFile,
|
||||||
|
runCLI,
|
||||||
|
runCommand,
|
||||||
|
uniq,
|
||||||
|
updateFile,
|
||||||
|
updateJson,
|
||||||
|
} from '@nx/e2e/utils';
|
||||||
|
describe('Next TS Solutions', () => {
|
||||||
|
let proj: string;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
proj = newProject({
|
||||||
|
packages: ['@nx/next'],
|
||||||
|
preset: 'ts',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterAll(() => cleanupProject());
|
||||||
|
|
||||||
|
it('should support importing a esm library', async () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
const libName = uniq('lib');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/next:app ${appName} --no-interactive --style=css --linter=none --unitTestRunner=none --e2eTestRunner=none`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/js:lib packages/${libName} --bundler=vite --no-interactive --unit-test-runner=none --skipFormat --linter=eslint`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`${appName}/src/app/page.tsx`,
|
||||||
|
`
|
||||||
|
import {${libName}} from '@${proj}/${libName}';
|
||||||
|
${readFile(`${appName}/src/app/page.tsx`)}
|
||||||
|
console.log(${libName}());
|
||||||
|
`
|
||||||
|
);
|
||||||
|
runCLI('sync');
|
||||||
|
|
||||||
|
// Add library to package.json to make sure it is linked (not needed for npm package manager)
|
||||||
|
updateJson(`${appName}/package.json`, (json) => {
|
||||||
|
return {
|
||||||
|
...json,
|
||||||
|
devDependencies: {
|
||||||
|
...(json.devDependencies || {}),
|
||||||
|
[`@${proj}/${libName}`]: 'workspace:*',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
runCommand(`cd ${appName} && ${getPackageManagerCommand().install}`);
|
||||||
|
|
||||||
|
const output = runCLI(`build ${appName}`);
|
||||||
|
expect(output).toContain(
|
||||||
|
`Successfully ran target build for project @${proj}/${appName}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
uniq,
|
uniq,
|
||||||
updateFile,
|
updateFile,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import * as http from 'http';
|
|
||||||
import { checkApp } from './utils';
|
import { checkApp } from './utils';
|
||||||
|
|
||||||
describe('Next.js Applications', () => {
|
describe('Next.js Applications', () => {
|
||||||
@ -240,20 +239,3 @@ describe('Next.js Applications', () => {
|
|||||||
expect(postBuildPagesContent).toMatchSnapshot();
|
expect(postBuildPagesContent).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getData(port, path = ''): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
http
|
|
||||||
.get(`http://localhost:${port}${path}`, (res) => {
|
|
||||||
expect(res.statusCode).toEqual(200);
|
|
||||||
let data = '';
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
res.once('end', () => {
|
|
||||||
resolve(data);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('error', (err) => reject(err));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -87,4 +87,47 @@ describe('React (TS solution)', () => {
|
|||||||
|
|
||||||
checkFilesExist(`packages/${appName}/dist/index.html`);
|
checkFilesExist(`packages/${appName}/dist/index.html`);
|
||||||
}, 90_000);
|
}, 90_000);
|
||||||
|
|
||||||
|
it('should be able to use Rspack to build apps with an imported lib', async () => {
|
||||||
|
const appName = uniq('app');
|
||||||
|
const libName = uniq('lib');
|
||||||
|
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/react:app packages/${appName} --bundler=rspack --no-interactive --skipFormat --linter=eslint --unitTestRunner=none`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`generate @nx/js:lib libs/${libName} --bundler=none --no-interactive --unit-test-runner=none --skipFormat --linter=eslint`
|
||||||
|
);
|
||||||
|
|
||||||
|
const mainPath = `packages/${appName}/src/main.tsx`;
|
||||||
|
updateFile(
|
||||||
|
mainPath,
|
||||||
|
`
|
||||||
|
import {${libName}} from '@${workspaceName}/${libName}';
|
||||||
|
${readFile(mainPath)}
|
||||||
|
console.log(${libName}());
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI('sync');
|
||||||
|
|
||||||
|
// Add library to package.json to make sure it is linked (not needed for npm package manager)
|
||||||
|
updateJson(`packages/${appName}/package.json`, (json) => {
|
||||||
|
return {
|
||||||
|
...json,
|
||||||
|
devDependencies: {
|
||||||
|
...(json.devDependencies || {}),
|
||||||
|
[`@${workspaceName}/${libName}`]: 'workspace:*',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
runCommand(
|
||||||
|
`cd packages/${appName} && ${getPackageManagerCommand().install}`
|
||||||
|
);
|
||||||
|
|
||||||
|
runCLI(`build ${appName}`);
|
||||||
|
|
||||||
|
checkFilesExist(`packages/${appName}/dist/index.html`);
|
||||||
|
}, 90_000);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -261,6 +261,17 @@ export function getNextConfig(
|
|||||||
},
|
},
|
||||||
...validNextConfig,
|
...validNextConfig,
|
||||||
webpack: (config, options) => {
|
webpack: (config, options) => {
|
||||||
|
/**
|
||||||
|
* To support ESM library export, we need to ensure the extensionAlias contains both `.js` and `.ts` extensions.
|
||||||
|
* This is because Webpack uses the `extensionAlias` to resolve the correct file extension when importing modules.
|
||||||
|
*/
|
||||||
|
config.resolve.extensionAlias = {
|
||||||
|
...(config.resolve.extensionAlias || {}),
|
||||||
|
'.js': ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
|
'.mjs': ['.mts', '.mjs'],
|
||||||
|
'.cjs': ['.cts', '.cjs'],
|
||||||
|
'.jsx': ['.tsx', '.jsx'],
|
||||||
|
};
|
||||||
/*
|
/*
|
||||||
* Update babel to support our monorepo setup.
|
* Update babel to support our monorepo setup.
|
||||||
* The 'upward' mode allows the root babel.config.json and per-project .babelrc files to be picked up.
|
* The 'upward' mode allows the root babel.config.json and per-project .babelrc files to be picked up.
|
||||||
|
|||||||
@ -27,6 +27,13 @@ const IGNORED_RSPACK_WARNINGS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const extensions = ['...', '.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
const extensions = ['...', '.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
||||||
|
|
||||||
|
const extensionAlias = {
|
||||||
|
'.js': ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
|
'.mjs': ['.mts', '.mjs'],
|
||||||
|
'.cjs': ['.cts', '.cjs'],
|
||||||
|
'.jsx': ['.tsx', '.jsx'],
|
||||||
|
};
|
||||||
const mainFields = ['module', 'main'];
|
const mainFields = ['module', 'main'];
|
||||||
|
|
||||||
export function applyBaseConfig(
|
export function applyBaseConfig(
|
||||||
@ -385,6 +392,10 @@ function applyNxDependentConfig(
|
|||||||
config.resolve = {
|
config.resolve = {
|
||||||
...config.resolve,
|
...config.resolve,
|
||||||
extensions: [...(config?.resolve?.extensions ?? []), ...extensions],
|
extensions: [...(config?.resolve?.extensions ?? []), ...extensions],
|
||||||
|
extensionAlias: {
|
||||||
|
...(config.resolve?.extensionAlias ?? {}),
|
||||||
|
...extensionAlias,
|
||||||
|
},
|
||||||
alias: {
|
alias: {
|
||||||
...(config.resolve?.alias ?? {}),
|
...(config.resolve?.alias ?? {}),
|
||||||
...(options.fileReplacements?.reduce(
|
...(options.fileReplacements?.reduce(
|
||||||
|
|||||||
@ -28,8 +28,10 @@ const IGNORED_WEBPACK_WARNINGS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const extensionAlias = {
|
const extensionAlias = {
|
||||||
'.js': ['.ts', '.js'],
|
'.js': ['.ts', '.tsx', '.js', '.jsx'],
|
||||||
'.mjs': ['.mts', '.mjs'],
|
'.mjs': ['.mts', '.mjs'],
|
||||||
|
'.cjs': ['.cts', '.cjs'],
|
||||||
|
'.jsx': ['.tsx', '.jsx'],
|
||||||
};
|
};
|
||||||
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
||||||
const mainFields = ['module', 'main'];
|
const mainFields = ['module', 'main'];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user