252 lines
6.9 KiB
TypeScript

import {
formatFiles,
GeneratorCallback,
joinPathFragments,
offsetFromRoot,
readNxJson,
readProjectConfiguration,
runTasksInSerial,
Tree,
updateProjectConfiguration,
writeJson,
} from '@nx/devkit';
import { webpackInitGenerator } from '../init/init';
import { ConfigurationGeneratorSchema } from './schema';
import { WebpackExecutorOptions } from '../../executors/webpack/schema';
import { hasPlugin } from '../../utils/has-plugin';
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
import { ensureDependencies } from '../../utils/ensure-dependencies';
export function configurationGenerator(
tree: Tree,
options: ConfigurationGeneratorSchema
) {
return configurationGeneratorInternal(tree, { addPlugin: false, ...options });
}
export async function configurationGeneratorInternal(
tree: Tree,
options: ConfigurationGeneratorSchema
) {
const tasks: GeneratorCallback[] = [];
const nxJson = readNxJson(tree);
const addPluginDefault =
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault;
const initTask = await webpackInitGenerator(tree, {
...options,
skipFormat: true,
});
tasks.push(initTask);
const depsTask = ensureDependencies(tree, {
compiler: options.compiler === 'babel' ? undefined : options.compiler,
});
tasks.push(depsTask);
checkForTargetConflicts(tree, options);
if (!hasPlugin(tree)) {
addBuildTarget(tree, options);
if (options.devServer) {
addServeTarget(tree, options);
}
}
createWebpackConfig(tree, options);
if (!options.skipFormat) {
await formatFiles(tree);
}
return runTasksInSerial(...tasks);
}
function checkForTargetConflicts(
tree: Tree,
options: ConfigurationGeneratorSchema
) {
if (options.skipValidation) return;
const project = readProjectConfiguration(tree, options.project);
if (project.targets?.build) {
throw new Error(
`Project "${project.name}" already has a build target. Pass --skipValidation to ignore this error.`
);
}
if (options.devServer && project.targets?.serve) {
throw new Error(
`Project "${project.name}" already has a serve target. Pass --skipValidation to ignore this error.`
);
}
}
function createWebpackConfig(
tree: Tree,
options: ConfigurationGeneratorSchema
) {
const project = readProjectConfiguration(tree, options.project);
const buildOptions: WebpackExecutorOptions = {
target: options.target,
outputPath: joinPathFragments('dist', project.root),
compiler: options.compiler ?? 'swc',
main: options.main ?? joinPathFragments(project.root, 'src/main.ts'),
tsConfig:
options.tsConfig ?? joinPathFragments(project.root, 'tsconfig.app.json'),
webpackConfig: joinPathFragments(project.root, 'webpack.config.js'),
};
if (options.target === 'web') {
tree.write(
joinPathFragments(project.root, 'webpack.config.js'),
hasPlugin(tree)
? `
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '${offsetFromRoot(project.root)}${
buildOptions.outputPath
}'),
},
plugins: [
new NxAppWebpackPlugin({
target: '${buildOptions.target}',
tsConfig: '${buildOptions.tsConfig}',
compiler: '${buildOptions.compiler}',
main: '${buildOptions.main}',
outputHashing: '${buildOptions.target !== 'web' ? 'none' : 'all'}',
})
],
}
`
: `
const { composePlugins, withNx, withWeb } = require('@nx/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;
});
`
);
} else {
tree.write(
joinPathFragments(project.root, 'webpack.config.js'),
hasPlugin(tree)
? `
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
const { join } = require('path');
module.exports = {
output: {
path: join(__dirname, '${offsetFromRoot(project.root)}${
buildOptions.outputPath
}'),
},
plugins: [
new NxAppWebpackPlugin({
target: '${buildOptions.target}',
tsConfig: '${buildOptions.tsConfig}',
compiler: '${buildOptions.compiler}',
main: '${buildOptions.main}',
outputHashing: '${buildOptions.target !== 'web' ? 'none' : 'all'}',
})
],
}
`
: `
const { composePlugins, withNx } = require('@nx/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;
});
`
);
}
}
function addBuildTarget(tree: Tree, options: ConfigurationGeneratorSchema) {
addBuildTargetDefaults(tree, '@nx/webpack:webpack');
const project = readProjectConfiguration(tree, options.project);
const buildOptions: WebpackExecutorOptions = {
target: options.target,
outputPath: joinPathFragments('dist', project.root),
compiler: options.compiler ?? 'swc',
main: options.main ?? joinPathFragments(project.root, 'src/main.ts'),
tsConfig:
options.tsConfig ?? joinPathFragments(project.root, 'tsconfig.app.json'),
webpackConfig: joinPathFragments(project.root, 'webpack.config.js'),
};
if (options.webpackConfig) {
buildOptions.webpackConfig = options.webpackConfig;
}
if (options.babelConfig) {
buildOptions.babelConfig = options.babelConfig;
} else if (options.compiler === 'babel') {
// If no babel config file is provided then write a default one, otherwise build will fail.
writeJson(tree, joinPathFragments(project.root, '.babelrc'), {
presets: ['@nx/js/babel'],
});
}
updateProjectConfiguration(tree, options.project, {
...project,
targets: {
...project.targets,
build: {
executor: '@nx/webpack:webpack',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
production: {
optimization: true,
outputHashing: options.target === 'web' ? 'all' : 'none',
sourceMap: false,
namedChunks: false,
extractLicenses: true,
vendorChunk: false,
},
},
},
},
});
}
function addServeTarget(tree: Tree, options: ConfigurationGeneratorSchema) {
const project = readProjectConfiguration(tree, options.project);
updateProjectConfiguration(tree, options.project, {
...project,
targets: {
...project.targets,
serve: {
executor: '@nx/webpack:dev-server',
options: {
buildTarget: `${options.project}:build`,
},
configurations: {
production: {
buildTarget: `${options.project}:build:production`,
},
},
},
},
});
}
export default configurationGenerator;