182 lines
4.9 KiB
TypeScript

import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
detectPackageManager,
readJsonFile,
TargetConfiguration,
workspaceRoot,
writeJsonFile,
} from '@nx/devkit';
import { basename, dirname, isAbsolute, join, relative } from 'path';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { WebpackExecutorOptions } from '../executors/webpack/schema';
import { WebDevServerOptions } from '../executors/dev-server/schema';
import { existsSync, readdirSync } from 'fs';
import { readWebpackOptions } from '../utils/webpack/read-webpack-options';
import { resolveUserDefinedWebpackConfig } from '../utils/webpack/resolve-user-defined-webpack-config';
import { getLockFileName, getRootTsConfigPath } from '@nx/js';
import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
export interface WebpackPluginOptions {
buildTargetName?: string;
serveTargetName?: string;
serveStaticTargetName?: string;
previewTargetName?: string;
}
const cachePath = join(projectGraphCacheDirectory, 'webpack.hash');
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};
const calculatedTargets: Record<
string,
Record<string, TargetConfiguration>
> = {};
function readTargetsCache(): Record<
string,
Record<string, TargetConfiguration>
> {
return readJsonFile(cachePath);
}
function writeTargetsToCache(
targets: Record<string, Record<string, TargetConfiguration>>
) {
writeJsonFile(cachePath, targets);
}
export const createDependencies: CreateDependencies = () => {
writeTargetsToCache(calculatedTargets);
return [];
};
export const createNodes: CreateNodes<WebpackPluginOptions> = [
'**/webpack.config.{js,ts,mjs,mts,cjs,cts}',
async (configFilePath, options, context) => {
options ??= {};
options.buildTargetName ??= 'build';
options.serveTargetName ??= 'serve';
options.serveStaticTargetName ??= 'serve-static';
options.previewTargetName ??= 'preview';
const projectRoot = dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
const hash = calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);
const targets = targetsCache[hash]
? targetsCache[hash]
: await createWebpackTargets(
configFilePath,
projectRoot,
options,
context
);
return {
projects: {
[projectRoot]: {
projectType: 'application',
targets,
},
},
};
},
];
async function createWebpackTargets(
configFilePath: string,
projectRoot: string,
options: WebpackPluginOptions,
context: CreateNodesContext
): Promise<
Record<
string,
TargetConfiguration<WebpackExecutorOptions | WebDevServerOptions>
>
> {
const namedInputs = getNamedInputs(projectRoot, context);
const webpackConfig = resolveUserDefinedWebpackConfig(
join(context.workspaceRoot, configFilePath),
getRootTsConfigPath()
);
const webpackOptions = await readWebpackOptions(webpackConfig);
const outputPath =
normalizeOutputPath(webpackOptions.output?.path) ??
'{workspaceRoot}/dist/{projectRoot}';
const targets = {};
const configBasename = basename(configFilePath);
targets[options.buildTargetName] = {
command: `webpack -c ${configBasename} --node-env=production`,
options: { cwd: projectRoot },
cache: true,
dependsOn: [`^${options.buildTargetName}`],
inputs:
'production' in namedInputs
? [
'default',
'^production',
{
externalDependencies: ['webpack-cli'],
},
]
: [
'default',
'^default',
{
externalDependencies: ['webpack-cli'],
},
],
outputs: [outputPath],
};
targets[options.serveTargetName] = {
command: `webpack serve -c ${configBasename} --node-env=development`,
options: {
cwd: projectRoot,
},
};
targets[options.previewTargetName] = {
command: `webpack serve -c ${configBasename} --node-env=production`,
options: {
cwd: projectRoot,
},
};
targets[options.serveStaticTargetName] = {
executor: '@nx/web:file-server',
options: {
buildTarget: options.buildTargetName,
},
};
return targets;
}
function normalizeOutputPath(
outputPath: string | undefined
): string | undefined {
if (!outputPath) return undefined;
if (isAbsolute(outputPath)) {
return `{workspaceRoot}/${relative(workspaceRoot, outputPath)}`;
} else {
return outputPath;
}
}