import {
joinPathFragments,
logger,
readProjectConfiguration,
TargetConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
import { RspackExecutorSchema } from '../executors/rspack/schema';
import { ConfigurationSchema } from '../generators/configuration/schema';
import { Framework } from '../generators/init/schema';
export type Target = 'build' | 'serve';
export type TargetFlags = Partial>;
export type UserProvidedTargetName = Partial>;
export type ValidFoundTargetName = Partial>;
export function findExistingTargetsInProject(
targets: {
[targetName: string]: TargetConfiguration;
},
userProvidedTargets?: UserProvidedTargetName
): {
validFoundTargetName: ValidFoundTargetName;
projectContainsUnsupportedExecutor: boolean;
userProvidedTargetIsUnsupported: TargetFlags;
alreadyHasNxRspackTargets: TargetFlags;
} {
const output: ReturnType = {
validFoundTargetName: {},
projectContainsUnsupportedExecutor: false,
userProvidedTargetIsUnsupported: {},
alreadyHasNxRspackTargets: {},
};
const supportedExecutors = {
build: [
'@nxext/vite:build',
'@nrwl/webpack:webpack',
'@nrwl/rollup:rollup',
'@nrwl/web:rollup',
'@nrwl/vite:build',
'@nx/webpack:webpack',
'@nx/rollup:rollup',
'@nx/web:rollup',
'@nx/vite:build',
],
serve: [
'@nxext/vite:dev',
'@nrwl/webpack:dev-server',
'@nrwl/vite:dev-server',
'@nx/webpack:dev-server',
'@nx/vite:dev-server',
],
};
const unsupportedExecutors = [
'@nx/js:babel',
'@nx/js:node',
'@nx/js:swc',
'@nx/react-native:run-ios',
'@nx/react-native:start',
'@nx/react-native:run-android',
'@nx/react-native:bundle',
'@nx/react-native:build-android',
'@nx/react-native:bundle',
'@nx/next:build',
'@nx/next:server',
'@nx/js:tsc',
'@nx/angular:ng-packagr-lite',
'@nx/angular:package',
'@nx/angular:webpack-browser',
'@nx/esbuild:esbuild',
'@nrwl/js:babel',
'@nrwl/js:node',
'@nrwl/js:swc',
'@nrwl/react-native:run-ios',
'@nrwl/react-native:start',
'@nrwl/react-native:run-android',
'@nrwl/react-native:bundle',
'@nrwl/react-native:build-android',
'@nrwl/react-native:bundle',
'@nrwl/next:build',
'@nrwl/next:server',
'@nrwl/js:tsc',
'@nrwl/angular:ng-packagr-lite',
'@nrwl/angular:package',
'@nrwl/angular:webpack-browser',
'@nrwl/esbuild:esbuild',
'@angular-devkit/build-angular:browser',
'@angular-devkit/build-angular:dev-server',
];
// First, we check if the user has provided a target
// If they have, we check if the executor the target is using is supported
// If it's not supported, then we set the unsupported flag to true for that target
function checkUserProvidedTarget(target: Target) {
if (userProvidedTargets?.[target]) {
if (
supportedExecutors[target].includes(
targets[userProvidedTargets[target]]?.executor
)
) {
output.validFoundTargetName[target] = userProvidedTargets[target];
} else {
output.userProvidedTargetIsUnsupported[target] = true;
}
}
}
checkUserProvidedTarget('build');
checkUserProvidedTarget('serve');
// Returns early when we have a build, serve, and test targets.
if (output.validFoundTargetName.build && output.validFoundTargetName.serve) {
return output;
}
// We try to find the targets that are using the supported executors
// for build, serve and test, since these are the ones we will be converting
for (const target in targets) {
const executorName = targets[target].executor;
const hasRspackTargets = output.alreadyHasNxRspackTargets;
hasRspackTargets.build ||= executorName === '@nx/rspack:rspack';
hasRspackTargets.serve ||= executorName === '@nx/rspack:dev-server';
const foundTargets = output.validFoundTargetName;
if (
!foundTargets.build &&
supportedExecutors.build.includes(executorName)
) {
foundTargets.build = target;
}
if (
!foundTargets.serve &&
supportedExecutors.serve.includes(executorName)
) {
foundTargets.serve = target;
}
output.projectContainsUnsupportedExecutor ||=
unsupportedExecutors.includes(executorName);
}
return output;
}
export function addOrChangeBuildTarget(
tree: Tree,
options: ConfigurationSchema,
target: string
) {
const project = readProjectConfiguration(tree, options.project);
const assets = [];
if (
options.target === 'web' &&
tree.exists(joinPathFragments(project.root, 'src/favicon.ico'))
) {
assets.push(joinPathFragments(project.root, 'src/favicon.ico'));
}
if (tree.exists(joinPathFragments(project.root, 'src/assets'))) {
assets.push(joinPathFragments(project.root, 'src/assets'));
}
const buildOptions: RspackExecutorSchema = {
target: options.target ?? 'web',
outputPath: joinPathFragments(
'dist',
// If standalone project then use the project's name in dist.
project.root === '.' ? project.name : project.root
),
main: determineMain(tree, options),
tsConfig: determineTsConfig(tree, options),
rspackConfig: joinPathFragments(project.root, 'rspack.config.js'),
assets,
};
project.targets ??= {};
project.targets[target] = {
executor: '@nx/rspack:rspack',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: buildOptions,
configurations: {
development: {
mode: 'development',
},
production: {
mode: 'production',
optimization: options.target === 'web' ? true : undefined,
sourceMap: false,
},
},
};
updateProjectConfiguration(tree, options.project, project);
}
export function addOrChangeServeTarget(
tree: Tree,
options: ConfigurationSchema,
target: string
) {
const project = readProjectConfiguration(tree, options.project);
project.targets ??= {};
project.targets[target] = {
executor: '@nx/rspack:dev-server',
options: {
buildTarget: `${options.project}:build:development`,
},
configurations: {
development: {},
production: {
buildTarget: `${options.project}:build:production`,
},
},
};
updateProjectConfiguration(tree, options.project, project);
}
export function writeRspackConfigFile(
tree: Tree,
options: ConfigurationSchema,
stylePreprocessorOptions?: { includePaths?: string[] }
) {
const project = readProjectConfiguration(tree, options.project);
tree.write(
joinPathFragments(project.root, 'rspack.config.js'),
createConfig(options, stylePreprocessorOptions)
);
}
function createConfig(
options: ConfigurationSchema,
stylePreprocessorOptions?: { includePaths?: string[] }
) {
if (options.framework === 'react') {
return `
const { composePlugins, withNx, withReact } = require('@nx/rspack');
module.exports = composePlugins(withNx(), withReact(${
stylePreprocessorOptions
? `
{
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
}
`
: ''
}), (config) => {
return config;
});
`;
} else if (options.framework === 'web' || options.target === 'web') {
return `
const { composePlugins, withNx, withWeb } = require('@nx/rspack');
module.exports = composePlugins(withNx(), withWeb(${
stylePreprocessorOptions
? `
{
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
}
`
: ''
}), (config) => {
return config;
});
`;
} else if (options.framework === 'nest') {
return `
const { composePlugins, withNx } = require('@nx/rspack');
module.exports = composePlugins(withNx(), (config) => {
return config;
});
`;
} else {
return `
const { composePlugins, withNx${
stylePreprocessorOptions ? ', withWeb' : ''
} } = require('@nx/rspack');
module.exports = composePlugins(withNx()${
stylePreprocessorOptions
? `,
withWeb({
stylePreprocessorOptions: ${JSON.stringify(stylePreprocessorOptions)},
})`
: ''
}, (config) => {
return config;
});
`;
}
}
export function deleteWebpackConfig(
tree: Tree,
projectRoot: string,
webpackConfigFilePath?: string
) {
const webpackConfigPath =
webpackConfigFilePath && tree.exists(webpackConfigFilePath)
? webpackConfigFilePath
: tree.exists(`${projectRoot}/webpack.config.js`)
? `${projectRoot}/webpack.config.js`
: tree.exists(`${projectRoot}/webpack.config.ts`)
? `${projectRoot}/webpack.config.ts`
: null;
if (webpackConfigPath) {
tree.delete(webpackConfigPath);
}
}
// Maybe add delete vite config?
export function moveAndEditIndexHtml(
tree: Tree,
options: ConfigurationSchema,
buildTarget: string
) {
const projectConfig = readProjectConfiguration(tree, options.project);
let indexHtmlPath =
projectConfig.targets?.[buildTarget]?.options?.index ??
`${projectConfig.root}/src/index.html`;
let mainPath =
projectConfig.targets?.[buildTarget]?.options?.main ??
`${projectConfig.root}/src/main.ts${
options.framework === 'react' ? 'x' : ''
}`;
if (projectConfig.root !== '.') {
mainPath = mainPath.replace(projectConfig.root, '');
}
if (
!tree.exists(indexHtmlPath) &&
tree.exists(`${projectConfig.root}/index.html`)
) {
indexHtmlPath = `${projectConfig.root}/index.html`;
}
if (tree.exists(indexHtmlPath)) {
const indexHtmlContent = tree.read(indexHtmlPath, 'utf8');
if (
!indexHtmlContent.includes(
``
)
) {
tree.write(
`${projectConfig.root}/index.html`,
indexHtmlContent.replace(
'
',
`
`
)
);
if (tree.exists(`${projectConfig.root}/src/index.html`)) {
tree.delete(`${projectConfig.root}/src/index.html`);
}
}
} else {
tree.write(
`${projectConfig.root}/index.html`,
`