knip
Version:
Find unused files, dependencies and exports in your TypeScript and JavaScript projects
114 lines (113 loc) • 4.93 kB
JavaScript
import { compact } from '../../util/array.js';
import { toDeferResolve, toDependency, toDevDependency, toEntry, toProductionEntry, } from '../../util/input.js';
import { isAbsolute, isInternal, join, relative } from '../../util/path.js';
import { hasDependency } from '../../util/plugin.js';
import { getDependenciesFromConfig } from '../babel/index.js';
const title = 'webpack';
const enablers = ['webpack', 'webpack-cli'];
const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
const config = ['webpack.config.{js,ts,mjs,cjs,mts,cts}'];
const hasBabelOptions = (use) => Boolean(use) &&
typeof use !== 'string' &&
'loader' in use &&
typeof use.loader === 'string' &&
use.loader === 'babel-loader' &&
typeof use.options === 'object';
const info = { compiler: '', issuer: '', realResource: '', resource: '', resourceQuery: '' };
const resolveRuleSetDependencies = (rule) => {
if (!rule || typeof rule === 'string')
return [];
if (typeof rule.use === 'string')
return [rule.use];
let useItem = rule.use ?? rule.loader ?? rule;
if (typeof useItem === 'function')
useItem = useItem(info);
if (typeof useItem === 'string' && hasBabelOptions(rule)) {
const d = getDependenciesFromConfig(rule.options).map(d => d.specifier);
return [useItem, ...d];
}
return [useItem].flat().flatMap((item) => {
if (!item)
return [];
if (hasBabelOptions(item)) {
const d = getDependenciesFromConfig(item.options).map(d => d.specifier);
return [...resolveUseItem(item), ...d];
}
if (typeof item !== 'string' && 'oneOf' in item)
return item.oneOf?.flatMap(resolveRuleSetDependencies) ?? [];
return resolveUseItem(item);
});
};
const resolveUseItem = (use) => {
if (!use)
return [];
if (typeof use === 'string')
return [use];
if ('loader' in use && typeof use.loader === 'string')
return [use.loader];
return [];
};
export const findWebpackDependenciesFromConfig = async ({ config, cwd }) => {
const passes = typeof config === 'function' ? [false, true] : [false];
const inputs = new Set();
for (const isProduction of passes) {
const mode = isProduction ? 'production' : 'development';
const env = { production: isProduction, mode };
const argv = { mode };
const resolvedConfig = typeof config === 'function' ? await config(env, argv) : config;
for (const options of [resolvedConfig].flat()) {
const entries = [];
for (const loader of options.module?.rules?.flatMap(resolveRuleSetDependencies) ?? []) {
inputs.add(toDeferResolve(loader.replace(/\?.*/, '')));
}
if (typeof options.entry === 'string')
entries.push(options.entry);
else if (Array.isArray(options.entry))
entries.push(...options.entry);
else if (typeof options.entry === 'object') {
for (const entry of Object.values(options.entry)) {
if (typeof entry === 'string')
entries.push(entry);
else if (Array.isArray(entry))
entries.push(...entry);
else if (typeof entry === 'function')
entries.push(entry());
else if (entry && typeof entry === 'object' && 'filename' in entry)
entries.push(entry['filename']);
}
}
for (const entry of entries) {
if (!isInternal(entry)) {
inputs.add(toDependency(entry));
}
else {
const absoluteEntry = isAbsolute(entry) ? entry : join(options.context ? options.context : cwd, entry);
const item = relative(cwd, absoluteEntry);
const value = options.mode === 'development' ? toEntry(item) : toProductionEntry(item);
inputs.add(value);
}
}
}
}
return inputs;
};
const resolveConfig = async (localConfig, options) => {
const { cwd, manifest } = options;
const inputs = await findWebpackDependenciesFromConfig({ config: localConfig, cwd });
const scripts = Object.values(manifest.scripts ?? {});
const webpackCLI = scripts.some(script => script && /(?<=^|\s)webpack(?=\s|$)/.test(script)) ? ['webpack-cli'] : [];
const webpackDevServer = scripts.some(script => script?.includes('webpack serve')) ? ['webpack-dev-server'] : [];
return compact([...inputs, [...webpackCLI, ...webpackDevServer].map(toDevDependency)].flat());
};
const args = {
binaries: ['webpack', 'webpack-dev-server'],
config: true,
};
export default {
title,
enablers,
isEnabled,
config,
resolveConfig,
args,
};