feat(webpack): generate React and Web apps with webpack.config.js file (#14285)

This commit is contained in:
Jack Hsu 2023-01-12 10:06:25 -05:00 committed by GitHub
parent 0925c294d1
commit 454fba49b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 404 additions and 291 deletions

View File

@ -0,0 +1,35 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
newProject,
runCLI,
runCLIAsync,
tmpProjPath,
uniq,
updateFile,
} from '@nrwl/e2e/utils';
import { execSync } from 'child_process';
describe('Node Applications + esbuild', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should generate an app using esbuild', async () => {
const app = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${app} --bundler=esbuild --no-interactive`);
checkFilesDoNotExist(`apps/${app}/webpack.config.js`);
updateFile(`apps/${app}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${app}`);
checkFilesExist(`dist/apps/${app}/main.cjs`);
const result = execSync(`node dist/apps/${app}/main.cjs`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toMatch(/Hello World!/);
}, 300_000);
});

View File

@ -0,0 +1,35 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
newProject,
runCLI,
runCLIAsync,
tmpProjPath,
uniq,
updateFile,
} from '@nrwl/e2e/utils';
import { execSync } from 'child_process';
describe('Node Applications + webpack', () => {
beforeEach(() => newProject());
afterEach(() => cleanupProject());
it('should generate an app using webpack', async () => {
const app = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${app} --bundler=webpack --no-interactive`);
checkFilesExist(`apps/${app}/webpack.config.js`);
updateFile(`apps/${app}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${app}`);
checkFilesExist(`dist/apps/${app}/main.js`);
const result = execSync(`node dist/apps/${app}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toMatch(/Hello World!/);
}, 300_000);
});

View File

@ -59,8 +59,8 @@ describe('Node Applications', () => {
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`); updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`); await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`); checkFilesExist(`dist/apps/${nodeapp}/main.cjs`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, { const result = execSync(`node dist/apps/${nodeapp}/main.cjs`, {
cwd: tmpProjPath(), cwd: tmpProjPath(),
}).toString(); }).toString();
expect(result).toContain('Hello World!'); expect(result).toContain('Hello World!');
@ -76,13 +76,15 @@ describe('Node Applications', () => {
}); });
await runCLIAsync(`build ${nodeapp}`); await runCLIAsync(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/index.js`); checkFilesExist(`dist/apps/${nodeapp}/index.cjs`);
}, 300000); }, 300000);
it('should be able to generate an empty application with additional entries', async () => { it('should be able to generate an empty application with additional entries', async () => {
const nodeapp = uniq('nodeapp'); const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp} --linter=eslint`); runCLI(
`generate @nrwl/node:app ${nodeapp} --linter=eslint --bundler=webpack`
);
const lintResults = runCLI(`lint ${nodeapp}`); const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('All files pass linting.'); expect(lintResults).toContain('All files pass linting.');
@ -267,7 +269,7 @@ describe('Build Node apps', () => {
expect(satisfies(packageJson.dependencies['tslib'], '^2.3.0')).toBeTruthy(); expect(satisfies(packageJson.dependencies['tslib'], '^2.3.0')).toBeTruthy();
const nodeapp = uniq('nodeapp'); const nodeapp = uniq('nodeapp');
runCLI(`generate @nrwl/node:app ${nodeapp}`); runCLI(`generate @nrwl/node:app ${nodeapp} --bundler=webpack`);
const jslib = uniq('jslib'); const jslib = uniq('jslib');
runCLI(`generate @nrwl/js:lib ${jslib} --buildable`); runCLI(`generate @nrwl/js:lib ${jslib} --buildable`);

View File

@ -48,11 +48,15 @@ describe('React Module Federation', () => {
updateFile( updateFile(
`apps/${shell}/webpack.config.js`, `apps/${shell}/webpack.config.js`,
stripIndents` stripIndents`
const { withModuleFederation } = require('@nrwl/react/module-federation'); import { ModuleFederationConfig } from '@nrwl/devkit';
const moduleFederationConfig = require('./module-federation.config'); import { composePlugins, withNx } from '@nrwl/webpack';
import { withReact } from '@nrwl/react';
import { withModuleFederation } from '@nrwl/react/module-federation');
module.exports = withModuleFederation({ const baseConfig = require('./module-federation.config');
...moduleFederationConfig,
const config: ModuleFederationConfig = {
...baseConfig,
remotes: [ remotes: [
'${remote1}', '${remote1}',
['${remote2}', 'http://localhost:${readPort( ['${remote2}', 'http://localhost:${readPort(
@ -60,7 +64,10 @@ describe('React Module Federation', () => {
)}/remoteEntry.js'], )}/remoteEntry.js'],
['${remote3}', 'http://localhost:${readPort(remote3)}'], ['${remote3}', 'http://localhost:${readPort(remote3)}'],
], ],
}); };
// Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config));
` `
); );

View File

@ -66,6 +66,7 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
const initTask = await initGenerator(tree, { ...options, skipFormat: true }); const initTask = await initGenerator(tree, { ...options, skipFormat: true });
const applicationTask = await nodeApplicationGenerator(tree, { const applicationTask = await nodeApplicationGenerator(tree, {
...schema, ...schema,
bundler: 'webpack',
skipFormat: true, skipFormat: true,
}); });
addMainFile(tree, options); addMainFile(tree, options);

View File

@ -43,5 +43,6 @@ export function toNodeApplicationGeneratorOptions(
tags: options.tags, tags: options.tags,
unitTestRunner: options.unitTestRunner, unitTestRunner: options.unitTestRunner,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
bundler: 'webpack', // Some features require webpack plugins such as TS transformers
}; };
} }

View File

@ -1,6 +1,11 @@
import * as devkit from '@nrwl/devkit'; import * as devkit from '@nrwl/devkit';
import { getProjects, NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit'; import {
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; getProjects,
readJson,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
// nx-ignore-next-line // nx-ignore-next-line
import { applicationGenerator as angularApplicationGenerator } from '@nrwl/angular/generators'; import { applicationGenerator as angularApplicationGenerator } from '@nrwl/angular/generators';
@ -13,7 +18,7 @@ describe('app', () => {
let tree: Tree; let tree: Tree;
beforeEach(() => { beforeEach(() => {
tree = createTreeWithEmptyV1Workspace(); tree = createTreeWithEmptyWorkspace();
overrideCollectionResolutionForTesting({ overrideCollectionResolutionForTesting({
'@nrwl/cypress': join(__dirname, '../../../../cypress/generators.json'), '@nrwl/cypress': join(__dirname, '../../../../cypress/generators.json'),
@ -32,27 +37,26 @@ describe('app', () => {
}); });
describe('not nested', () => { describe('not nested', () => {
it('should update workspace.json', async () => { it('should update project config', async () => {
await applicationGenerator(tree, { await applicationGenerator(tree, {
name: 'myNodeApp', name: 'myNodeApp',
standaloneConfig: false, standaloneConfig: false,
}); });
const workspaceJson = readJson(tree, '/workspace.json'); const project = readProjectConfiguration(tree, 'my-node-app');
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json'); expect(project.root).toEqual('my-node-app');
const project = workspaceJson.projects['my-node-app']; expect(project.targets).toEqual(
expect(project.root).toEqual('apps/my-node-app');
expect(project.architect).toEqual(
expect.objectContaining({ expect.objectContaining({
build: { build: {
builder: '@nrwl/webpack:webpack', executor: '@nrwl/webpack:webpack',
outputs: ['{options.outputPath}'], outputs: ['{options.outputPath}'],
options: { options: {
target: 'node', target: 'node',
compiler: 'tsc', compiler: 'tsc',
outputPath: 'dist/apps/my-node-app', outputPath: 'dist/my-node-app',
main: 'apps/my-node-app/src/main.ts', main: 'my-node-app/src/main.ts',
tsConfig: 'apps/my-node-app/tsconfig.app.json', tsConfig: 'my-node-app/tsconfig.app.json',
assets: ['apps/my-node-app/src/assets'], webpackConfig: 'my-node-app/webpack.config.js',
assets: ['my-node-app/src/assets'],
}, },
configurations: { configurations: {
production: { production: {
@ -63,7 +67,7 @@ describe('app', () => {
}, },
}, },
serve: { serve: {
builder: '@nrwl/js:node', executor: '@nrwl/js:node',
options: { options: {
buildTarget: 'my-node-app:build', buildTarget: 'my-node-app:build',
}, },
@ -75,14 +79,16 @@ describe('app', () => {
}, },
}) })
); );
expect(workspaceJson.projects['my-node-app'].architect.lint).toEqual({ expect(project.targets.lint).toEqual({
builder: '@nrwl/linter:eslint', executor: '@nrwl/linter:eslint',
outputs: ['{options.outputFile}'], outputs: ['{options.outputFile}'],
options: { options: {
lintFilePatterns: ['apps/my-node-app/**/*.ts'], lintFilePatterns: ['my-node-app/**/*.ts'],
}, },
}); });
expect(workspaceJson.projects['my-node-app-e2e']).toBeUndefined(); expect(() => readProjectConfiguration(tree, 'my-node-app-e2e')).toThrow(
/Cannot find/
);
}); });
it('should update tags', async () => { it('should update tags', async () => {
@ -104,13 +110,13 @@ describe('app', () => {
name: 'myNodeApp', name: 'myNodeApp',
standaloneConfig: false, standaloneConfig: false,
}); });
expect(tree.exists(`apps/my-node-app/jest.config.ts`)).toBeTruthy(); expect(tree.exists(`my-node-app/jest.config.ts`)).toBeTruthy();
expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy(); expect(tree.exists('my-node-app/src/main.ts')).toBeTruthy();
const tsconfig = readJson(tree, 'apps/my-node-app/tsconfig.json'); const tsconfig = readJson(tree, 'my-node-app/tsconfig.json');
expect(tsconfig).toMatchInlineSnapshot(` expect(tsconfig).toMatchInlineSnapshot(`
Object { Object {
"extends": "../../tsconfig.base.json", "extends": "../tsconfig.base.json",
"files": Array [], "files": Array [],
"include": Array [], "include": Array [],
"references": Array [ "references": Array [
@ -124,19 +130,19 @@ describe('app', () => {
} }
`); `);
const tsconfigApp = readJson(tree, 'apps/my-node-app/tsconfig.app.json'); const tsconfigApp = readJson(tree, 'my-node-app/tsconfig.app.json');
expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc'); expect(tsconfigApp.compilerOptions.outDir).toEqual('../dist/out-tsc');
expect(tsconfigApp.extends).toEqual('./tsconfig.json'); expect(tsconfigApp.extends).toEqual('./tsconfig.json');
expect(tsconfigApp.exclude).toEqual([ expect(tsconfigApp.exclude).toEqual([
'jest.config.ts', 'jest.config.ts',
'src/**/*.spec.ts', 'src/**/*.spec.ts',
'src/**/*.test.ts', 'src/**/*.test.ts',
]); ]);
const eslintrc = readJson(tree, 'apps/my-node-app/.eslintrc.json'); const eslintrc = readJson(tree, 'my-node-app/.eslintrc.json');
expect(eslintrc).toMatchInlineSnapshot(` expect(eslintrc).toMatchInlineSnapshot(`
Object { Object {
"extends": Array [ "extends": Array [
"../../.eslintrc.json", "../.eslintrc.json",
], ],
"ignorePatterns": Array [ "ignorePatterns": Array [
"!**/*", "!**/*",
@ -178,36 +184,33 @@ describe('app', () => {
standaloneConfig: false, standaloneConfig: false,
}); });
const tsconfig = readJson(tree, 'apps/my-node-app/tsconfig.json'); const tsconfig = readJson(tree, 'my-node-app/tsconfig.json');
expect(tsconfig.extends).toBe('../../tsconfig.json'); expect(tsconfig.extends).toBe('../tsconfig.json');
}); });
}); });
describe('nested', () => { describe('nested', () => {
it('should update workspace.json', async () => { it('should update project config', async () => {
await applicationGenerator(tree, { await applicationGenerator(tree, {
name: 'myNodeApp', name: 'myNodeApp',
directory: 'myDir', directory: 'myDir',
standaloneConfig: false, standaloneConfig: false,
}); });
const workspaceJson = readJson(tree, '/workspace.json'); const project = readProjectConfiguration(tree, 'my-dir-my-node-app');
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(workspaceJson.projects['my-dir-my-node-app'].root).toEqual( expect(project.root).toEqual('my-dir/my-node-app');
'apps/my-dir/my-node-app'
);
expect( expect(project.targets.lint).toEqual({
workspaceJson.projects['my-dir-my-node-app'].architect.lint executor: '@nrwl/linter:eslint',
).toEqual({
builder: '@nrwl/linter:eslint',
outputs: ['{options.outputFile}'], outputs: ['{options.outputFile}'],
options: { options: {
lintFilePatterns: ['apps/my-dir/my-node-app/**/*.ts'], lintFilePatterns: ['my-dir/my-node-app/**/*.ts'],
}, },
}); });
expect(workspaceJson.projects['my-dir-my-node-app-e2e']).toBeUndefined(); expect(() =>
readProjectConfiguration(tree, 'my-dir-my-node-app-e2e')
).toThrow(/Cannot find/);
}); });
it('should update tags', async () => { it('should update tags', async () => {
@ -239,8 +242,8 @@ describe('app', () => {
// Make sure these exist // Make sure these exist
[ [
`apps/my-dir/my-node-app/jest.config.ts`, `my-dir/my-node-app/jest.config.ts`,
'apps/my-dir/my-node-app/src/main.ts', 'my-dir/my-node-app/src/main.ts',
].forEach((path) => { ].forEach((path) => {
expect(tree.exists(path)).toBeTruthy(); expect(tree.exists(path)).toBeTruthy();
}); });
@ -248,17 +251,17 @@ describe('app', () => {
// Make sure these have properties // Make sure these have properties
[ [
{ {
path: 'apps/my-dir/my-node-app/tsconfig.app.json', path: 'my-dir/my-node-app/tsconfig.app.json',
lookupFn: (json) => json.compilerOptions.outDir, lookupFn: (json) => json.compilerOptions.outDir,
expectedValue: '../../../dist/out-tsc', expectedValue: '../../dist/out-tsc',
}, },
{ {
path: 'apps/my-dir/my-node-app/tsconfig.app.json', path: 'my-dir/my-node-app/tsconfig.app.json',
lookupFn: (json) => json.compilerOptions.types, lookupFn: (json) => json.compilerOptions.types,
expectedValue: ['node'], expectedValue: ['node'],
}, },
{ {
path: 'apps/my-dir/my-node-app/tsconfig.app.json', path: 'my-dir/my-node-app/tsconfig.app.json',
lookupFn: (json) => json.exclude, lookupFn: (json) => json.exclude,
expectedValue: [ expectedValue: [
'jest.config.ts', 'jest.config.ts',
@ -267,9 +270,9 @@ describe('app', () => {
], ],
}, },
{ {
path: 'apps/my-dir/my-node-app/.eslintrc.json', path: 'my-dir/my-node-app/.eslintrc.json',
lookupFn: (json) => json.extends, lookupFn: (json) => json.extends,
expectedValue: ['../../../.eslintrc.json'], expectedValue: ['../../.eslintrc.json'],
}, },
].forEach(hasJsonValue); ].forEach(hasJsonValue);
}); });
@ -283,21 +286,18 @@ describe('app', () => {
standaloneConfig: false, standaloneConfig: false,
}); });
expect(tree.exists('jest.config.ts')).toBeFalsy(); expect(tree.exists('jest.config.ts')).toBeFalsy();
expect(tree.exists('apps/my-node-app/src/test-setup.ts')).toBeFalsy(); expect(tree.exists('my-node-app/src/test-setup.ts')).toBeFalsy();
expect(tree.exists('apps/my-node-app/src/test.ts')).toBeFalsy(); expect(tree.exists('my-node-app/src/test.ts')).toBeFalsy();
expect(tree.exists('apps/my-node-app/tsconfig.spec.json')).toBeFalsy(); expect(tree.exists('my-node-app/tsconfig.spec.json')).toBeFalsy();
expect(tree.exists('apps/my-node-app/jest.config.ts')).toBeFalsy(); expect(tree.exists('my-node-app/jest.config.ts')).toBeFalsy();
const workspaceJson = readJson(tree, 'workspace.json'); const project = readProjectConfiguration(tree, 'my-node-app');
expect( expect(project.targets.test).toBeUndefined();
workspaceJson.projects['my-node-app'].architect.test expect(project.targets.lint).toMatchInlineSnapshot(`
).toBeUndefined();
expect(workspaceJson.projects['my-node-app'].architect.lint)
.toMatchInlineSnapshot(`
Object { Object {
"builder": "@nrwl/linter:eslint", "executor": "@nrwl/linter:eslint",
"options": Object { "options": Object {
"lintFilePatterns": Array [ "lintFilePatterns": Array [
"apps/my-node-app/**/*.ts", "my-node-app/**/*.ts",
], ],
}, },
"outputs": Array [ "outputs": Array [
@ -318,12 +318,10 @@ describe('app', () => {
standaloneConfig: false, standaloneConfig: false,
}); });
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); expect(tree.exists('my-frontend/proxy.conf.json')).toBeTruthy();
const serve = readJson(tree, 'workspace.json').projects['my-frontend'] const project = readProjectConfiguration(tree, 'my-frontend');
.architect.serve; const serve = project.targets.serve;
expect(serve.options.proxyConfig).toEqual( expect(serve.options.proxyConfig).toEqual('my-frontend/proxy.conf.json');
'apps/my-frontend/proxy.conf.json'
);
}); });
it('should configure proxies for multiple node projects with the same frontend app', async () => { it('should configure proxies for multiple node projects with the same frontend app', async () => {
@ -341,9 +339,9 @@ describe('app', () => {
standaloneConfig: false, standaloneConfig: false,
}); });
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); expect(tree.exists('my-frontend/proxy.conf.json')).toBeTruthy();
expect(readJson(tree, 'apps/my-frontend/proxy.conf.json')).toEqual({ expect(readJson(tree, 'my-frontend/proxy.conf.json')).toEqual({
'/api': { target: 'http://localhost:3333', secure: false }, '/api': { target: 'http://localhost:3333', secure: false },
'/billing-api': { target: 'http://localhost:3333', secure: false }, '/billing-api': { target: 'http://localhost:3333', secure: false },
}); });
@ -358,12 +356,10 @@ describe('app', () => {
standaloneConfig: false, standaloneConfig: false,
}); });
expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); expect(tree.exists('my-frontend/proxy.conf.json')).toBeTruthy();
const serve = readJson(tree, 'workspace.json').projects['my-frontend'] const project = readProjectConfiguration(tree, 'my-frontend');
.architect.serve; const serve = project.targets.serve;
expect(serve.options.proxyConfig).toEqual( expect(serve.options.proxyConfig).toEqual('my-frontend/proxy.conf.json');
'apps/my-frontend/proxy.conf.json'
);
}); });
}); });
@ -375,18 +371,18 @@ describe('app', () => {
babelJest: true, babelJest: true,
} as Schema); } as Schema);
expect(tree.read(`apps/my-node-app/jest.config.ts`, 'utf-8')) expect(tree.read(`my-node-app/jest.config.ts`, 'utf-8'))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"/* eslint-disable */ "/* eslint-disable */
export default { export default {
displayName: 'my-node-app', displayName: 'my-node-app',
preset: '../../jest.preset.js', preset: '../jest.preset.js',
testEnvironment: 'node', testEnvironment: 'node',
transform: { transform: {
'^.+\\\\\\\\.[tj]s$': 'babel-jest' '^.+\\\\\\\\.[tj]s$': 'babel-jest'
}, },
moduleFileExtensions: ['ts', 'js', 'html'], moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/my-node-app' coverageDirectory: '../coverage/my-node-app'
}; };
" "
`); `);
@ -399,15 +395,15 @@ describe('app', () => {
js: true, js: true,
} as Schema); } as Schema);
expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists(`my-node-app/jest.config.js`)).toBeTruthy();
expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); expect(tree.exists('my-node-app/src/main.js')).toBeTruthy();
const tsConfig = readJson(tree, 'apps/my-node-app/tsconfig.json'); const tsConfig = readJson(tree, 'my-node-app/tsconfig.json');
expect(tsConfig.compilerOptions).toEqual({ expect(tsConfig.compilerOptions).toEqual({
allowJs: true, allowJs: true,
}); });
const tsConfigApp = readJson(tree, 'apps/my-node-app/tsconfig.app.json'); const tsConfigApp = readJson(tree, 'my-node-app/tsconfig.app.json');
expect(tsConfigApp.include).toEqual(['src/**/*.ts', 'src/**/*.js']); expect(tsConfigApp.include).toEqual(['src/**/*.ts', 'src/**/*.js']);
expect(tsConfigApp.exclude).toEqual([ expect(tsConfigApp.exclude).toEqual([
'jest.config.ts', 'jest.config.ts',
@ -418,16 +414,15 @@ describe('app', () => {
]); ]);
}); });
it('should update workspace.json', async () => { it('should add project config', async () => {
await applicationGenerator(tree, { await applicationGenerator(tree, {
name: 'myNodeApp', name: 'myNodeApp',
js: true, js: true,
} as Schema); } as Schema);
const workspaceJson = readJson(tree, '/workspace.json'); const project = readProjectConfiguration(tree, 'my-node-app');
const project = workspaceJson.projects['my-node-app']; const buildTarget = project.targets.build;
const buildTarget = project.architect.build;
expect(buildTarget.options.main).toEqual('apps/my-node-app/src/main.js'); expect(buildTarget.options.main).toEqual('my-node-app/src/main.js');
}); });
it('should generate js files for nested libs as well', async () => { it('should generate js files for nested libs as well', async () => {
@ -436,10 +431,8 @@ describe('app', () => {
directory: 'myDir', directory: 'myDir',
js: true, js: true,
} as Schema); } as Schema);
expect( expect(tree.exists(`my-dir/my-node-app/jest.config.js`)).toBeTruthy();
tree.exists(`apps/my-dir/my-node-app/jest.config.js`) expect(tree.exists('my-dir/my-node-app/src/main.js')).toBeTruthy();
).toBeTruthy();
expect(tree.exists('apps/my-dir/my-node-app/src/main.js')).toBeTruthy();
}); });
}); });

View File

@ -65,6 +65,10 @@ function getWebpackBuildConfig(
), ),
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
assets: [joinPathFragments(project.sourceRoot, 'assets')], assets: [joinPathFragments(project.sourceRoot, 'assets')],
webpackConfig: joinPathFragments(
options.appProjectRoot,
'webpack.config.js'
),
}, },
configurations: { configurations: {
production: { production: {
@ -151,6 +155,10 @@ function addAppFiles(tree: Tree, options: NormalizedSchema) {
} }
); );
if (options.bundler !== 'webpack') {
tree.delete(joinPathFragments(options.appProjectRoot, 'webpack.config.js'));
}
if (options.framework) { if (options.framework) {
generateFiles( generateFiles(
tree, tree,
@ -358,11 +366,6 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
const appProjectRoot = joinPathFragments(appsDir, appDirectory); const appProjectRoot = joinPathFragments(appsDir, appDirectory);
if (options.framework) {
options.bundler = options.bundler ?? 'esbuild';
} else {
options.bundler = 'webpack';
}
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())

View File

@ -0,0 +1,8 @@
const { composePlugins, withNx} = require('@nrwl/webpack');
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
return config;
});

View File

@ -22,3 +22,4 @@ export { cypressComponentConfigGenerator } from './src/generators/cypress-compon
export { componentTestGenerator } from './src/generators/component-test/component-test'; export { componentTestGenerator } from './src/generators/component-test/component-test';
export { setupTailwindGenerator } from './src/generators/setup-tailwind/setup-tailwind'; export { setupTailwindGenerator } from './src/generators/setup-tailwind/setup-tailwind';
export type { SupportedStyles } from './typings/style'; export type { SupportedStyles } from './typings/style';
export * from './plugins/with-react';

View File

@ -1,4 +1,5 @@
import { import {
ExecutorContext,
joinPathFragments, joinPathFragments,
logger, logger,
readJsonFile, readJsonFile,
@ -15,7 +16,7 @@ import { gte } from 'semver';
import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack'; import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack';
import * as mergeWebpack from 'webpack-merge'; import * as mergeWebpack from 'webpack-merge';
import { mergePlugins } from './merge-plugins'; import { mergePlugins } from './merge-plugins';
import { withReact } from '../webpack'; import { withReact } from '../with-react';
import { withNx, withWeb } from '@nrwl/webpack'; import { withNx, withWeb } from '@nrwl/webpack';
// This is shamelessly taken from CRA and modified for NX use // This is shamelessly taken from CRA and modified for NX use
@ -128,7 +129,10 @@ export const webpack = async (
withWeb(), withWeb(),
withReact() withReact()
); );
const finalConfig = configure(baseWebpackConfig, { options: builderOptions }); const finalConfig = configure(baseWebpackConfig, {
options: builderOptions,
context: {} as ExecutorContext, // The context is not used here.
});
// Check whether the project .babelrc uses @emotion/babel-plugin. There's currently // Check whether the project .babelrc uses @emotion/babel-plugin. There's currently
// a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently // a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently

View File

@ -1,69 +1,4 @@
import type { Configuration } from 'webpack'; import { withReact } from './with-react';
import ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
import { NormalizedWebpackExecutorOptions } from '@nrwl/webpack';
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
// Add React-specific configuration
export function withReact() {
return function configure(
config: Configuration,
_ctx?: {
options: NormalizedWebpackExecutorOptions;
context: ExecutorContext;
}
): Configuration {
config.module.rules.push({
test: /\.svg$/,
issuer: /\.(js|ts|md)x?$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: '[name].[hash].[ext]',
},
},
],
});
if (config.mode === 'development' && config['devServer']?.hot) {
// add `react-refresh/babel` to babel loader plugin
const babelLoader = config.module.rules.find(
(rule) =>
typeof rule !== 'string' &&
rule.loader?.toString().includes('babel-loader')
);
if (babelLoader && typeof babelLoader !== 'string') {
babelLoader.options['plugins'] = [
...(babelLoader.options['plugins'] || []),
[
require.resolve('react-refresh/babel'),
{
skipEnvCheck: true,
},
],
];
}
// add https://github.com/pmmmwh/react-refresh-webpack-plugin to webpack plugin
config.plugins.push(new ReactRefreshPlugin());
}
// enable webpack node api
config.node = {
__dirname: true,
__filename: true,
};
return config;
};
}
// Support existing default exports as well as new named export. // Support existing default exports as well as new named export.
const legacyExport: any = withReact(); const legacyExport: any = withReact();

View File

@ -0,0 +1,69 @@
import type { Configuration } from 'webpack';
import { NormalizedWebpackExecutorOptions } from '@nrwl/webpack';
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
const processed = new Set();
export function withReact() {
return function configure(
config: Configuration,
_ctx?: {
options: NormalizedWebpackExecutorOptions;
context: ExecutorContext;
}
): Configuration {
if (processed.has(config)) return config;
config.module.rules.push({
test: /\.svg$/,
issuer: /\.(js|ts|md)x?$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
svgo: false,
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: '[name].[hash].[ext]',
},
},
],
});
if (config.mode === 'development' && config['devServer']?.hot) {
// add `react-refresh/babel` to babel loader plugin
const babelLoader = config.module.rules.find(
(rule) =>
typeof rule !== 'string' &&
rule.loader?.toString().includes('babel-loader')
);
if (babelLoader && typeof babelLoader !== 'string') {
babelLoader.options['plugins'] = [
...(babelLoader.options['plugins'] || []),
[
require.resolve('react-refresh/babel'),
{
skipEnvCheck: true,
},
],
];
}
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
config.plugins.push(new ReactRefreshPlugin());
}
// enable webpack node api
config.node = {
__dirname: true,
__filename: true,
};
processed.add(config);
return config;
};
}

View File

@ -314,7 +314,7 @@ describe('app', () => {
scripts: [], scripts: [],
styles: ['apps/my-app/src/styles.css'], styles: ['apps/my-app/src/styles.css'],
tsConfig: 'apps/my-app/tsconfig.app.json', tsConfig: 'apps/my-app/tsconfig.app.json',
webpackConfig: '@nrwl/react/plugins/webpack', webpackConfig: 'apps/my-app/webpack.config.js',
}); });
expect(targetConfig.build.configurations.production).toEqual({ expect(targetConfig.build.configurations.production).toEqual({
optimization: true, optimization: true,
@ -792,7 +792,7 @@ describe('app', () => {
expect( expect(
workspaceJson.get('my-app').targets.build.options.webpackConfig workspaceJson.get('my-app').targets.build.options.webpackConfig
).toEqual('@nrwl/react/plugins/webpack'); ).toEqual('apps/my-app/webpack.config.js');
}); });
it('should NOT add custom webpack config if bundler is vite', async () => { it('should NOT add custom webpack config if bundler is vite', async () => {

View File

@ -0,0 +1,9 @@
const { composePlugins, withNx } = require('@nrwl/webpack');
const { withReact } = require('@nrwl/react');
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
return config;
});

View File

@ -72,7 +72,10 @@ function createBuildTarget(options: NormalizedSchema): TargetConfiguration {
), ),
], ],
scripts: [], scripts: [],
webpackConfig: '@nrwl/react/plugins/webpack', webpackConfig: joinPathFragments(
options.appProjectRoot,
'webpack.config.js'
),
}, },
configurations: { configurations: {
development: { development: {

View File

@ -9,15 +9,15 @@ import { getAppTests } from './get-app-tests';
export function createApplicationFiles(host: Tree, options: NormalizedSchema) { export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
let styleSolutionSpecificAppFiles: string; let styleSolutionSpecificAppFiles: string;
if (options.styledModule && options.style !== 'styled-jsx') { if (options.styledModule && options.style !== 'styled-jsx') {
styleSolutionSpecificAppFiles = '../files/styled-module'; styleSolutionSpecificAppFiles = '../files/style-styled-module';
} else if (options.style === 'styled-jsx') { } else if (options.style === 'styled-jsx') {
styleSolutionSpecificAppFiles = '../files/styled-jsx'; styleSolutionSpecificAppFiles = '../files/style-styled-jsx';
} else if (options.style === 'none') { } else if (options.style === 'none') {
styleSolutionSpecificAppFiles = '../files/none'; styleSolutionSpecificAppFiles = '../files/style-none';
} else if (options.globalCss) { } else if (options.globalCss) {
styleSolutionSpecificAppFiles = '../files/global-css'; styleSolutionSpecificAppFiles = '../files/style-global-css';
} else { } else {
styleSolutionSpecificAppFiles = '../files/css-module'; styleSolutionSpecificAppFiles = '../files/style-css-module';
} }
const relativePathToRootTsConfig = getRelativePathToRootTsConfig( const relativePathToRootTsConfig = getRelativePathToRootTsConfig(
@ -38,7 +38,9 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) {
host, host,
join( join(
__dirname, __dirname,
options.bundler === 'vite' ? '../files/common-vite' : '../files/common' options.bundler === 'vite'
? '../files/base-vite'
: '../files/base-webpack'
), ),
options.appProjectRoot, options.appProjectRoot,
templateVariables templateVariables

View File

@ -1,8 +1,12 @@
const { withModuleFederationForSSR } = require('@nrwl/react/module-federation'); import { composePlugins, withNx } from '@nrwl/webpack';
const baseConfig = require("./module-federation.server.config"); import { withReact } from '@nrwl/react';
import { withModuleFederationForSSR } from '@nrwl/react/module-federation';
const baseConfig = require('./module-federation.config');
const defaultConfig = { const defaultConfig = {
...baseConfig ...baseConfig
}; };
module.exports = withModuleFederationForSSR(defaultConfig); // Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederationForSSR(defaultConfig));

View File

@ -1,13 +1,6 @@
// @ts-check module.exports = {
/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const moduleFederationConfig = {
name: '<%= projectName %>', name: '<%= projectName %>',
remotes: [ remotes: [
<% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %> <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
], ],
}; };
module.exports = moduleFederationConfig;

View File

@ -1,13 +1,12 @@
// @ts-check const { composePlugins, withNx } = require('@nrwl/webpack');
const { withReact } = require('@nrwl/react');
const { withModuleFederation } = require('@nrwl/react/module-federation'); const { withModuleFederation } = require('@nrwl/react/module-federation');
const baseConfig = require('./module-federation.config'); const baseConfig = require('./module-federation.config');
/** const config = {
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const defaultConfig = {
...baseConfig, ...baseConfig,
}; };
module.exports = withModuleFederation(defaultConfig); // Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config));

View File

@ -1,11 +1,9 @@
// @ts-check const { composePlugins, withNx } = require('@nrwl/webpack');
const { withReact } = require('@nrwl/react');
const { withModuleFederation } = require('@nrwl/react/module-federation'));
const { withModuleFederation } = require('@nrwl/react/module-federation');
const baseConfig = require('./module-federation.config'); const baseConfig = require('./module-federation.config');
/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const prodConfig = { const prodConfig = {
...baseConfig, ...baseConfig,
/* /*
@ -30,4 +28,5 @@ const prodConfig = {
], ],
}; };
module.exports = withModuleFederation(prodConfig); // Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(prodConfig));

View File

@ -1,13 +1,6 @@
// @ts-check module.exports = {
/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const moduleFederationConfig = {
name: '<%= projectName %>', name: '<%= projectName %>',
exposes: { exposes: {
'./Module': '<%= appProjectRoot %>/src/remote-entry.ts', './Module': '<%= appProjectRoot %>/src/remote-entry.ts',
}, },
}; };
module.exports = moduleFederationConfig;

View File

@ -1,8 +1,12 @@
const { composePlugins, withNx } = require('@nrwl/webpack');
const { withReact } = require('@nrwl/react');
const { withModuleFederationForSSR } = require('@nrwl/react/module-federation'); const { withModuleFederationForSSR } = require('@nrwl/react/module-federation');
const baseConfig = require("./module-federation.server.config"); const baseConfig = require("./module-federation.server.config");
const defaultConfig = { const defaultConfig = {
...baseConfig ...baseConfig,
}; };
module.exports = withModuleFederationForSSR(defaultConfig); // Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederationForSSR(defaultConfig));

View File

@ -1,13 +1,6 @@
// @ts-check module.exports = {
/**
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const moduleFederationConfig = {
name: '<%= projectName %>', name: '<%= projectName %>',
exposes: { exposes: {
'./Module': './src/remote-entry.ts', './Module': './src/remote-entry.ts',
}, },
}; };
module.exports = moduleFederationConfig;

View File

@ -1,13 +1,12 @@
// @ts-check const { composePlugins, withNx } = require('@nrwl/webpack');
const { withReact } = require('@nrwl/react');
const { withModuleFederation } = require('@nrwl/react/module-federation'));
const { withModuleFederation } = require('@nrwl/react/module-federation');
const baseConfig = require('./module-federation.config'); const baseConfig = require('./module-federation.config');
/** const config = {
* @type {import('@nrwl/devkit').ModuleFederationConfig}
**/
const defaultConfig = {
...baseConfig, ...baseConfig,
}; };
module.exports = withModuleFederation(defaultConfig); // Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(withNx(), withReact(), withModuleFederation(config));

View File

@ -107,7 +107,7 @@ export async function setupSsrGenerator(tree: Tree, options: Schema) {
compiler: 'babel', compiler: 'babel',
externalDependencies: 'all', externalDependencies: 'all',
outputHashing: 'none', outputHashing: 'none',
webpackConfig: '@nrwl/react/plugins/webpack', webpackConfig: joinPathFragments(projectRoot, 'webpack.config.js'),
}, },
configurations: { configurations: {
development: { development: {

View File

@ -351,6 +351,7 @@ describe('app', () => {
scripts: [], scripts: [],
styles: ['apps/my-app/src/styles.css'], styles: ['apps/my-app/src/styles.css'],
tsConfig: 'apps/my-app/tsconfig.app.json', tsConfig: 'apps/my-app/tsconfig.app.json',
webpackConfig: 'apps/my-app/webpack.config.js',
}); });
expect(architectConfig.build.configurations.production).toEqual({ expect(architectConfig.build.configurations.production).toEqual({
optimization: true, optimization: true,

View File

@ -44,7 +44,7 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) {
tree, tree,
join( join(
__dirname, __dirname,
options.bundler === 'vite' ? './files/app-vite' : './files/app' options.bundler === 'vite' ? './files/app-vite' : './files/app-webpack'
), ),
options.appProjectRoot, options.appProjectRoot,
{ {
@ -81,6 +81,10 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) {
tsConfig, tsConfig,
compiler: options.compiler ?? 'babel', compiler: options.compiler ?? 'babel',
devServer: true, devServer: true,
webpackConfig: joinPathFragments(
options.appProjectRoot,
'webpack.config.js'
),
}); });
const project = readProjectConfiguration(tree, options.projectName); const project = readProjectConfiguration(tree, options.projectName);
const prodConfig = project.targets.build.configurations.production; const prodConfig = project.targets.build.configurations.production;

View File

@ -0,0 +1,8 @@
const { composePlugins, withNx, withWeb } = require('@nrwl/webpack');
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withWeb(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
return config;
});

View File

@ -1,13 +0,0 @@
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -56,7 +56,7 @@ export async function* devServerExecutor(
); );
} }
let webpackConfig = getDevServerConfig(context, buildOptions, serveOptions); let config = getDevServerConfig(context, buildOptions, serveOptions);
if (buildOptions.webpackConfig) { if (buildOptions.webpackConfig) {
let customWebpack = resolveCustomWebpackConfig( let customWebpack = resolveCustomWebpackConfig(
@ -68,16 +68,17 @@ export async function* devServerExecutor(
customWebpack = await customWebpack; customWebpack = await customWebpack;
} }
webpackConfig = await customWebpack(webpackConfig, { config = await customWebpack(config, {
buildOptions, options: buildOptions,
context,
configuration: serveOptions.buildTarget.split(':')[2], configuration: serveOptions.buildTarget.split(':')[2],
}); });
} }
return yield* eachValueFrom( return yield* eachValueFrom(
runWebpackDevServer(webpackConfig, webpack, WebpackDevServer).pipe( runWebpackDevServer(config, webpack, WebpackDevServer).pipe(
tap(({ stats }) => { tap(({ stats }) => {
console.info(stats.toString((webpackConfig as any).stats)); console.info(stats.toString((config as any).stats));
}), }),
map(({ baseUrl, stats }) => { map(({ baseUrl, stats }) => {
return { return {

View File

@ -83,6 +83,7 @@ export interface WebpackExecutorOptions {
export interface NormalizedWebpackExecutorOptions export interface NormalizedWebpackExecutorOptions
extends WebpackExecutorOptions { extends WebpackExecutorOptions {
outputFileName: string;
assets?: AssetGlobPattern[]; assets?: AssetGlobPattern[];
root?: string; root?: string;
projectRoot?: string; projectRoot?: string;

View File

@ -1,37 +1,35 @@
import { type Compiler, sources, type WebpackPluginInstance } from 'webpack'; import { type Compiler, sources, type WebpackPluginInstance } from 'webpack';
import { import {
createLockFile,
createPackageJson,
ExecutorContext, ExecutorContext,
type ProjectGraph, type ProjectGraph,
serializeJson, serializeJson,
createPackageJson,
createLockFile,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { import {
getHelperDependenciesFromProjectGraph, getHelperDependenciesFromProjectGraph,
HelperDependency, HelperDependency,
} from '@nrwl/js/src/utils/compiler-helper-dependency'; } from '@nrwl/js/src/utils/compiler-helper-dependency';
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema';
import { getLockFileName } from 'nx/src/lock-file/lock-file'; import { getLockFileName } from 'nx/src/lock-file/lock-file';
export class GeneratePackageJsonWebpackPlugin implements WebpackPluginInstance { const pluginName = 'GeneratePackageJsonPlugin';
export class GeneratePackageJsonPlugin implements WebpackPluginInstance {
private readonly projectGraph: ProjectGraph; private readonly projectGraph: ProjectGraph;
constructor( constructor(
private readonly context: ExecutorContext, private readonly options: { tsConfig: string; outputFileName: string },
private readonly options: NormalizedWebpackExecutorOptions private readonly context: ExecutorContext
) { ) {
this.projectGraph = context.projectGraph; this.projectGraph = context.projectGraph;
} }
apply(compiler: Compiler): void { apply(compiler: Compiler): void {
const pluginName = this.constructor.name;
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
compilation.hooks.processAssets.tap( compilation.hooks.processAssets.tap(
{ {
name: 'nx-generate-package-json-plugin', name: pluginName,
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
}, },
() => { () => {

View File

@ -17,18 +17,18 @@ export function getBaseWebpackPartial(
export type NxWebpackPlugin = ( export type NxWebpackPlugin = (
config: Configuration, config: Configuration,
ctx?: { ctx: {
options: NormalizedWebpackExecutorOptions; options: NormalizedWebpackExecutorOptions;
context?: ExecutorContext; context: ExecutorContext;
} }
) => Configuration; ) => Configuration;
export function composePlugins(...plugins: NxWebpackPlugin[]) { export function composePlugins(...plugins: NxWebpackPlugin[]) {
return function combined( return function combined(
config: Configuration, config: Configuration,
ctx?: { ctx: {
options: NormalizedWebpackExecutorOptions; options: NormalizedWebpackExecutorOptions;
context?: ExecutorContext; context: ExecutorContext;
} }
): Configuration { ): Configuration {
for (const plugin of plugins) { for (const plugin of plugins) {

View File

@ -1,5 +1,9 @@
export function tsNodeRegister(file: string = '', tsConfig?: string) { export function tsNodeRegister(file: string = '', tsConfig?: string) {
if (!file?.endsWith('.ts')) return; if (!file?.endsWith('.ts')) return;
// Avoid double-registering which can lead to issues type-checking already transformed files.
if (isRegistered()) return;
// Register TS compiler lazily // Register TS compiler lazily
require('ts-node').register({ require('ts-node').register({
project: tsConfig, project: tsConfig,
@ -29,3 +33,9 @@ export function resolveCustomWebpackConfig(path: string, tsConfig: string) {
// `{ default: { ... } }` // `{ default: { ... } }`
return customWebpackConfig.default || customWebpackConfig; return customWebpackConfig.default || customWebpackConfig;
} }
export function isRegistered() {
return (
require.extensions['.ts'] != undefined ||
require.extensions['.tsx'] != undefined
);
}

View File

@ -11,7 +11,7 @@ import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema'; import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema';
import { StatsJsonPlugin } from '../plugins/stats-json-plugin'; import { StatsJsonPlugin } from '../plugins/stats-json-plugin';
import { createCopyPlugin } from './create-copy-plugin'; import { createCopyPlugin } from './create-copy-plugin';
import { GeneratePackageJsonWebpackPlugin } from '../plugins/generate-package-json-webpack-plugin'; import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin';
import { getOutputHashFormat } from './hash-format'; import { getOutputHashFormat } from './hash-format';
const IGNORED_WEBPACK_WARNINGS = [ const IGNORED_WEBPACK_WARNINGS = [
@ -22,6 +22,8 @@ const IGNORED_WEBPACK_WARNINGS = [
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx']; const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
const mainFields = ['main', 'module']; const mainFields = ['main', 'module'];
const processed = new Set();
export function withNx(opts?: { skipTypeChecking?: boolean }) { export function withNx(opts?: { skipTypeChecking?: boolean }) {
return function configure( return function configure(
config: Configuration, config: Configuration,
@ -33,7 +35,10 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
context: ExecutorContext; context: ExecutorContext;
} }
): Configuration { ): Configuration {
if (processed.has(config)) return config;
const plugins: WebpackPluginInstance[] = []; const plugins: WebpackPluginInstance[] = [];
if (!opts?.skipTypeChecking) { if (!opts?.skipTypeChecking) {
plugins.push( plugins.push(
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
@ -88,7 +93,7 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
} }
if (options.generatePackageJson && context) { if (options.generatePackageJson && context) {
plugins.push(new GeneratePackageJsonWebpackPlugin(context, options)); plugins.push(new GeneratePackageJsonPlugin(options, context));
} }
if (options.statsJson) { if (options.statsJson) {
@ -118,7 +123,7 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
? `[name]${hashFormat.chunk}.js` ? `[name]${hashFormat.chunk}.js`
: '[name].js'; : '[name].js';
return { const updated = {
...config, ...config,
target: options.target, target: options.target,
node: false as const, node: false as const,
@ -132,14 +137,14 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
mode: mode:
process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'production' process.env.NODE_ENV === 'production'
? process.env.NODE_ENV ? (process.env.NODE_ENV as 'development' | 'production')
: 'none', : ('none' as const),
devtool: devtool:
options.sourceMap === 'hidden' options.sourceMap === 'hidden'
? 'hidden-source-map' ? 'hidden-source-map'
: options.sourceMap : options.sourceMap
? 'source-map' ? 'source-map'
: false, : (false as const),
entry, entry,
output: { output: {
...config.output, ...config.output,
@ -150,7 +155,7 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
hashFunction: 'xxhash64', hashFunction: 'xxhash64',
// Disabled for performance // Disabled for performance
pathinfo: false, pathinfo: false,
scriptType: 'module', scriptType: 'module' as const,
}, },
watch: options.watch, watch: options.watch,
watchOptions: { watchOptions: {
@ -186,6 +191,7 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
? new TerserPlugin({ ? new TerserPlugin({
parallel: true, parallel: true,
terserOptions: { terserOptions: {
keep_classnames: true,
ecma: 2020, ecma: 2020,
safari10: true, safari10: true,
output: { output: {
@ -208,7 +214,7 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
}, },
performance: { performance: {
...config.performance, ...config.performance,
hints: false, hints: false as const,
}, },
experiments: { ...config.experiments, cacheUnaffected: true }, experiments: { ...config.experiments, cacheUnaffected: true },
ignoreWarnings: [ ignoreWarnings: [
@ -269,6 +275,9 @@ export function withNx(opts?: { skipTypeChecking?: boolean }) {
usedExports: !!options.verbose, usedExports: !!options.verbose,
}, },
}; };
processed.add(updated);
return updated;
}; };
} }

View File

@ -7,6 +7,7 @@ import {
} from 'webpack'; } from 'webpack';
import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity'; import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
import * as path from 'path'; import * as path from 'path';
import { basename } from 'path';
import { getOutputHashFormat } from '@nrwl/webpack/src/utils/hash-format'; import { getOutputHashFormat } from '@nrwl/webpack/src/utils/hash-format';
import { PostcssCliResources } from '@nrwl/webpack/src/utils/webpack/plugins/postcss-cli-resources'; import { PostcssCliResources } from '@nrwl/webpack/src/utils/webpack/plugins/postcss-cli-resources';
import { normalizeExtraEntryPoints } from '@nrwl/webpack/src/utils/webpack/normalize-entry'; import { normalizeExtraEntryPoints } from '@nrwl/webpack/src/utils/webpack/normalize-entry';
@ -19,7 +20,6 @@ import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
import MiniCssExtractPlugin = require('mini-css-extract-plugin'); import MiniCssExtractPlugin = require('mini-css-extract-plugin');
import autoprefixer = require('autoprefixer'); import autoprefixer = require('autoprefixer');
import postcssImports = require('postcss-import'); import postcssImports = require('postcss-import');
import { basename } from 'path';
interface PostcssOptions { interface PostcssOptions {
(loader: any): any; (loader: any): any;
@ -27,11 +27,15 @@ interface PostcssOptions {
config?: string; config?: string;
} }
const processed = new Set();
export function withWeb() { export function withWeb() {
return function configure( return function configure(
config: Configuration, config: Configuration,
{ options }: { options: NormalizedWebpackExecutorOptions } { options }: { options: NormalizedWebpackExecutorOptions }
): Configuration { ): Configuration {
if (processed.has(config)) return config;
const plugins = []; const plugins = [];
const stylesOptimization = const stylesOptimization =
@ -246,9 +250,6 @@ export function withWeb() {
}) })
); );
// context needs to be set for babel to pick up correct babelrc
config.context = path.join(options.root, options.projectRoot);
config.output = { config.output = {
...config.output, ...config.output,
crossOriginLoading: options.subresourceIntegrity crossOriginLoading: options.subresourceIntegrity
@ -303,7 +304,7 @@ export function withWeb() {
config.module = { config.module = {
...config.module, ...config.module,
rules: [ rules: [
...config.module.rules, ...(config.module.rules ?? []),
...rules, ...rules,
{ {
test: /\.(bmp|png|jpe?g|gif|webp|avif)$/, test: /\.(bmp|png|jpe?g|gif|webp|avif)$/,
@ -323,6 +324,7 @@ export function withWeb() {
}, },
], ],
}; };
processed.add(config);
return config; return config;
}; };
} }