This PR fixes and issue where the standard `output.clean` option is
ignored and replaced by the Nx-specific `deleteOutputPath` option on the
`NxAppWebpackPlugin` and `NxAppRspackPlugin` plugins.
We want to allow users to use standards over our own features, so if we
see that `output.clean` is set in webpack/rspack config, then we use
that value.
For example, an Rspack config could be:
```js
const { NxAppRspackPlugin } = require("@nx/rspack/app-plugin");
const { join } = require("path");
module.exports = {
output: {
path: join(__dirname, "dist/demo"),
clean: false, // <-- THIS DOES NOT WORK!
},
plugins: [
new NxAppRspackPlugin({
// ...
}),
],
};
```
But even though `output.clean` is `false`, each build will still delete
`dist`. The only way to disable that behavior is to use the Nx-specific
option like this:
```js
const { NxAppRspackPlugin } = require("@nx/rspack/app-plugin");
const { join } = require("path");
module.exports = {
output: {
path: join(__dirname, "dist/demo"),
},
plugins: [
new NxAppRspackPlugin({
deleteOutputPath: false,
// ...
}),
],
};
```
## Current Behavior
Setting `output.clean` in Webpack/Rspack config does nothing, and we
always default our own `deleteOutputPath` to `true`.
## Expected Behavior
Setting `output.clean` standard option is respected.
## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
Fixes #
619 lines
18 KiB
TypeScript
619 lines
18 KiB
TypeScript
import {
|
|
checkFilesExist,
|
|
cleanupProject,
|
|
createFile,
|
|
fileExists,
|
|
listFiles,
|
|
newProject,
|
|
packageInstall,
|
|
readFile,
|
|
rmDist,
|
|
runCLI,
|
|
runCommand,
|
|
uniq,
|
|
updateFile,
|
|
updateJson,
|
|
} from '@nx/e2e/utils';
|
|
import { join } from 'path';
|
|
|
|
describe('Webpack Plugin', () => {
|
|
beforeAll(() => newProject());
|
|
afterAll(() => cleanupProject());
|
|
|
|
it('should be able to setup project to build node programs with webpack and different compilers', async () => {
|
|
const myPkg = uniq('my-pkg');
|
|
runCLI(
|
|
`generate @nx/js:lib ${myPkg} --directory=libs/${myPkg} --bundler=none`
|
|
);
|
|
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
|
|
|
|
runCLI(
|
|
`generate @nx/webpack:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts`
|
|
);
|
|
|
|
// Test `scriptType` later during during.
|
|
updateFile(
|
|
`libs/${myPkg}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
class DebugPlugin {
|
|
apply(compiler) {
|
|
console.log('scriptType is ' + compiler.options.output.scriptType);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/libs/${myPkg}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: './src/index.ts',
|
|
tsConfig: './tsconfig.lib.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
}),
|
|
new DebugPlugin()
|
|
]
|
|
};`
|
|
);
|
|
|
|
rmDist();
|
|
|
|
const buildOutput = runCLI(`build ${myPkg}`);
|
|
// Ensure scriptType is not set if we're in Node (it only applies to Web).
|
|
expect(buildOutput).toContain('scriptType is undefined');
|
|
let output = runCommand(`node dist/libs/${myPkg}/main.js`);
|
|
expect(output).toMatch(/Hello/);
|
|
expect(output).not.toMatch(/Conflicting/);
|
|
expect(output).not.toMatch(/process.env.NODE_ENV/);
|
|
|
|
updateJson(join('libs', myPkg, 'project.json'), (config) => {
|
|
delete config.targets.build;
|
|
return config;
|
|
});
|
|
|
|
// swc
|
|
runCLI(
|
|
`generate @nx/webpack:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=swc`
|
|
);
|
|
rmDist();
|
|
runCLI(`build ${myPkg}`);
|
|
output = runCommand(`node dist/libs/${myPkg}/main.js`);
|
|
expect(output).toMatch(/Hello/);
|
|
|
|
updateJson(join('libs', myPkg, 'project.json'), (config) => {
|
|
delete config.targets.build;
|
|
return config;
|
|
});
|
|
|
|
// tsc
|
|
runCLI(
|
|
`generate @nx/webpack:configuration ${myPkg} --target=node --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts --compiler=tsc`
|
|
);
|
|
rmDist();
|
|
runCLI(`build ${myPkg}`);
|
|
output = runCommand(`node dist/libs/${myPkg}/main.js`);
|
|
expect(output).toMatch(/Hello/);
|
|
}, 500000);
|
|
|
|
it('should use either BABEL_ENV or NODE_ENV value for Babel environment configuration', async () => {
|
|
const myPkg = uniq('my-pkg');
|
|
runCLI(
|
|
`generate @nx/js:lib ${myPkg} --directory=libs/${myPkg} --bundler=none`
|
|
);
|
|
updateFile(`libs/${myPkg}/src/index.ts`, `console.log('Hello');\n`);
|
|
|
|
runCLI(
|
|
`generate @nx/webpack:configuration ${myPkg} --target=node --compiler=babel --tsConfig=libs/${myPkg}/tsconfig.lib.json --main=libs/${myPkg}/src/index.ts`
|
|
);
|
|
|
|
updateFile(
|
|
`libs/${myPkg}/.babelrc`,
|
|
`{ 'presets': ['@nx/js/babel', './custom-preset'] } `
|
|
);
|
|
updateFile(
|
|
`libs/${myPkg}/custom-preset.js`,
|
|
`
|
|
module.exports = function(api, opts) {
|
|
console.log('Babel env is ' + api.env());
|
|
return opts;
|
|
}
|
|
`
|
|
);
|
|
|
|
let output = runCLI(`build ${myPkg}`, {
|
|
env: {
|
|
NODE_ENV: 'nodeEnv',
|
|
BABEL_ENV: 'babelEnv',
|
|
},
|
|
});
|
|
expect(output).toContain('Babel env is babelEnv');
|
|
}, 500_000);
|
|
|
|
it('should be able to build with NxWebpackPlugin and a standard webpack config file', () => {
|
|
const appName = uniq('app');
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --bundler webpack --directory=apps/${appName}`
|
|
);
|
|
updateFile(`apps/${appName}/src/main.ts`, `console.log('Hello');\n`);
|
|
updateFile(`apps/${appName}/src/foo.ts`, `console.log('Foo');\n`);
|
|
updateFile(`apps/${appName}/src/bar.ts`, `console.log('Bar');\n`);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = {
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/apps/${appName}'),
|
|
// do not remove dist, so files between builds will remain
|
|
clean: false,
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: './src/main.ts',
|
|
additionalEntryPoints: [
|
|
'./src/foo.ts',
|
|
{
|
|
entryName: 'bar',
|
|
entryPath: './src/bar.ts',
|
|
}
|
|
],
|
|
tsConfig: './tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
})
|
|
]
|
|
};`
|
|
);
|
|
|
|
runCLI(`build ${appName}`);
|
|
|
|
expect(runCommand(`node dist/apps/${appName}/main.js`)).toMatch(/Hello/);
|
|
expect(runCommand(`node dist/apps/${appName}/foo.js`)).toMatch(/Foo/);
|
|
expect(runCommand(`node dist/apps/${appName}/bar.js`)).toMatch(/Bar/);
|
|
|
|
// Ensure dist is not removed between builds since output.clean === false
|
|
createFile(`dist/apps/${appName}/extra.js`);
|
|
runCLI(`build ${appName} --skip-nx-cache`);
|
|
checkFilesExist(`dist/apps/${appName}/extra.js`);
|
|
}, 500_000);
|
|
|
|
it('should bundle in NX_PUBLIC_ environment variables', () => {
|
|
const appName = uniq('app');
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --directory=apps/${appName} --bundler webpack`
|
|
);
|
|
|
|
checkFilesExist(`apps/${appName}/src/main.ts`);
|
|
updateFile(
|
|
`apps/${appName}/src/main.ts`,
|
|
`
|
|
console.log(process.env['NX_PUBLIC_TEST']);
|
|
`
|
|
);
|
|
|
|
runCLI(`build ${appName}`, {
|
|
env: {
|
|
NX_PUBLIC_TEST: 'foobar',
|
|
},
|
|
});
|
|
|
|
const mainFile = listFiles(`dist/apps/${appName}`).filter((f) =>
|
|
f.startsWith('main.')
|
|
);
|
|
const content = readFile(`dist/apps/${appName}/${mainFile}`);
|
|
expect(content).toMatch(/foobar/);
|
|
});
|
|
|
|
it('should support babel + core-js to polyfill JS features', async () => {
|
|
const appName = uniq('app');
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --directory=apps/${appName} --bundler webpack --compiler=babel`
|
|
);
|
|
packageInstall('core-js', undefined, '3.26.1', 'prod');
|
|
|
|
checkFilesExist(`apps/${appName}/src/main.ts`);
|
|
updateFile(
|
|
`apps/${appName}/src/main.ts`,
|
|
`
|
|
import 'core-js/stable';
|
|
async function main() {
|
|
const result = await Promise.resolve('foobar')
|
|
console.log(result);
|
|
}
|
|
main();
|
|
`
|
|
);
|
|
|
|
// Modern browser
|
|
updateFile(`apps/${appName}/.browserslistrc`, `last 1 Chrome version\n`);
|
|
runCLI(`build ${appName}`);
|
|
expect(readMainFile(`dist/apps/${appName}`)).toMatch(`await Promise`);
|
|
|
|
// Legacy browser
|
|
updateFile(`apps/${appName}/.browserslistrc`, `IE 11\n`);
|
|
runCLI(`build ${appName}`);
|
|
expect(readMainFile(`dist/apps/${appName}`)).not.toMatch(`await Promise`);
|
|
});
|
|
|
|
it('should allow options to be passed from the executor', async () => {
|
|
const appName = uniq('app');
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --directory=apps/${appName} --bundler webpack`
|
|
);
|
|
|
|
checkFilesExist(`apps/${appName}/project.json`);
|
|
updateJson(`apps/${appName}/project.json`, (json) => {
|
|
json.targets.build = {
|
|
executor: '@nx/webpack:webpack',
|
|
outputs: ['{options.outputPath}'],
|
|
options: {
|
|
generatePackageJson: true, // This should be passed to the plugin.
|
|
outputPath: `dist/apps/${appName}`,
|
|
webpackConfig: `apps/${appName}/webpack.config.js`,
|
|
},
|
|
};
|
|
return json;
|
|
});
|
|
|
|
checkFilesExist(`apps/${appName}/webpack.config.js`);
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
const { join } = require('path');
|
|
module.exports = {
|
|
output: {
|
|
path: join(__dirname, '../../dist/apps/demo'),
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
// NOTE: generatePackageJson is missing here, but executor passes it.
|
|
target: 'web',
|
|
compiler: 'swc',
|
|
main: './src/main.ts',
|
|
tsConfig: './tsconfig.app.json',
|
|
optimization: false,
|
|
outputHashing: 'none',
|
|
}),
|
|
],
|
|
};`
|
|
);
|
|
|
|
runCLI(`build ${appName}`);
|
|
|
|
fileExists(`dist/apps/${appName}/package.json`);
|
|
});
|
|
|
|
it('should resolve assets from executors as relative to workspace root', () => {
|
|
const appName = uniq('app');
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --directory=apps/${appName} --bundler webpack`
|
|
);
|
|
updateFile('shared/docs/TEST.md', 'TEST');
|
|
updateJson(`apps/${appName}/project.json`, (json) => {
|
|
json.targets.build = {
|
|
executor: '@nx/webpack:webpack',
|
|
outputs: ['{options.outputPath}'],
|
|
options: {
|
|
assets: [
|
|
{
|
|
input: 'shared/docs',
|
|
glob: 'TEST.md',
|
|
output: '.',
|
|
},
|
|
],
|
|
outputPath: `dist/apps/${appName}`,
|
|
webpackConfig: `apps/${appName}/webpack.config.js`,
|
|
},
|
|
};
|
|
return json;
|
|
});
|
|
|
|
runCLI(`build ${appName}`);
|
|
|
|
checkFilesExist(`dist/apps/${appName}/TEST.md`);
|
|
});
|
|
|
|
it('it should support building libraries and apps when buildLibsFromSource is false', () => {
|
|
const appName = uniq('app');
|
|
const myPkg = uniq('my-pkg');
|
|
|
|
runCLI(
|
|
`generate @nx/web:application ${appName} --directory=apps/${appName}`
|
|
);
|
|
|
|
runCLI(
|
|
`generate @nx/js:lib ${myPkg} --directory=libs/${myPkg} --importPath=@${appName}/${myPkg}`
|
|
);
|
|
|
|
updateFile(`libs/${myPkg}/src/index.ts`, `export const foo = 'bar';\n`);
|
|
|
|
updateFile(
|
|
`apps/${appName}/src/main.ts`,
|
|
`import { foo } from '@${appName}/${myPkg}';\nconsole.log(foo);\n`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = {
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${appName}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: 'apps/${appName}/src/main.ts',
|
|
tsConfig: 'apps/${appName}/tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
buildLibsFromSource: false,
|
|
})
|
|
]
|
|
};`
|
|
);
|
|
|
|
const result = runCLI(`build ${appName}`);
|
|
|
|
expect(result).toContain(
|
|
`Running target build for project ${appName} and 1 task it depends on`
|
|
);
|
|
expect(result).toContain(`nx run ${myPkg}:build`);
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName} and 1 task it depends on`
|
|
);
|
|
});
|
|
|
|
it('should be able to support webpack config with nx enhanced and babel', () => {
|
|
const appName = uniq('app');
|
|
|
|
runCLI(
|
|
`generate @nx/web:app ${appName} --directory=apps/${appName} --bundler=webpack --compiler=babel`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const { composePlugins, withNx } = require('@nx/webpack');
|
|
const { withReact } = require('@nx/react');
|
|
const { join } = require('path');
|
|
|
|
const pluginOption = {
|
|
index: 'apps/${appName}/src/index.html',
|
|
main: 'apps/${appName}/src/main.ts',
|
|
tsConfig: 'apps/${appName}/tsconfig.app.json',
|
|
outputPath: 'dist/apps/${appName}',
|
|
}
|
|
|
|
// Nx composable plugins for webpack.
|
|
module.exports = composePlugins(
|
|
withNx(pluginOption),
|
|
withReact(pluginOption),
|
|
);`
|
|
);
|
|
|
|
const result = runCLI(`build ${appName}`);
|
|
|
|
expect(result).toContain(`nx run ${appName}:build`);
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName}`
|
|
);
|
|
});
|
|
|
|
describe('config types', () => {
|
|
it('should support a standard config object', () => {
|
|
const appName = uniq('app');
|
|
|
|
runCLI(
|
|
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = {
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${appName}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'babel',
|
|
main: './src/main.tsx',
|
|
tsConfig: './tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
})
|
|
]
|
|
};`
|
|
);
|
|
|
|
const result = runCLI(`build ${appName}`);
|
|
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName}`
|
|
);
|
|
});
|
|
|
|
it('should support a standard function that returns a config object', () => {
|
|
const appName = uniq('app');
|
|
|
|
runCLI(
|
|
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = () => {
|
|
return {
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${appName}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: './src/main.tsx',
|
|
tsConfig: './tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
})
|
|
]
|
|
};
|
|
};`
|
|
);
|
|
|
|
const result = runCLI(`build ${appName}`);
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName}`
|
|
);
|
|
});
|
|
|
|
it('should support an array of standard config objects', () => {
|
|
const appName = uniq('app');
|
|
const serverName = uniq('server');
|
|
|
|
runCLI(
|
|
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
|
|
);
|
|
|
|
// Create server index file
|
|
createFile(
|
|
`apps/${serverName}/index.js`,
|
|
`console.log('Hello from ${serverName}');\n`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = [
|
|
{
|
|
name: 'client',
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${appName}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: './src/main.tsx',
|
|
tsConfig: './tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
})
|
|
]
|
|
}, {
|
|
name: 'server',
|
|
target: 'node',
|
|
entry: '../${serverName}/index.js',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${serverName}'),
|
|
filename: 'index.js',
|
|
},
|
|
}
|
|
];
|
|
`
|
|
);
|
|
|
|
const result = runCLI(`build ${appName}`);
|
|
|
|
checkFilesExist(`dist/${appName}/main.js`);
|
|
checkFilesExist(`dist/${serverName}/index.js`);
|
|
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName}`
|
|
);
|
|
});
|
|
|
|
it('should support a function that returns an array of standard config objects', () => {
|
|
const appName = uniq('app');
|
|
const serverName = uniq('server');
|
|
|
|
runCLI(
|
|
`generate @nx/react:application --directory=apps/${appName} --bundler=webpack --e2eTestRunner=none`
|
|
);
|
|
|
|
// Create server index file
|
|
createFile(
|
|
`apps/${serverName}/index.js`,
|
|
`console.log('Hello from ${serverName}');\n`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${appName}/webpack.config.js`,
|
|
`
|
|
const path = require('path');
|
|
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
|
|
|
module.exports = () => {
|
|
return [
|
|
{
|
|
name: 'client',
|
|
target: 'node',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${appName}')
|
|
},
|
|
plugins: [
|
|
new NxAppWebpackPlugin({
|
|
compiler: 'tsc',
|
|
main: './src/main.tsx',
|
|
tsConfig: './tsconfig.app.json',
|
|
outputHashing: 'none',
|
|
optimization: false,
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'server',
|
|
target: 'node',
|
|
entry: '../${serverName}/index.js',
|
|
output: {
|
|
path: path.join(__dirname, '../../dist/${serverName}'),
|
|
filename: 'index.js',
|
|
}
|
|
}
|
|
];
|
|
};`
|
|
);
|
|
const result = runCLI(`build ${appName}`);
|
|
|
|
checkFilesExist(`dist/${serverName}/index.js`);
|
|
checkFilesExist(`dist/${appName}/main.js`);
|
|
|
|
expect(result).toContain(
|
|
`Successfully ran target build for project ${appName}`
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
function readMainFile(dir: string): string {
|
|
const main = listFiles(dir).find((f) => f.startsWith('main.'));
|
|
return readFile(`${dir}/${main}`);
|
|
}
|