UNPKG

knip

Version:

Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects

160 lines (159 loc) 6.74 kB
import { compact } from '../../util/array.js'; import { toAlias, toDeferResolve, toDeferResolveEntry, toDeferResolveProductionEntry, toDependency, } from '../../util/input.js'; import { isInternal, join, toAbsolute } 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: '', dependency: '', descriptionData: {}, issuerLayer: '', }; 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, options) => { const { cwd, isProduction } = options; const passes = typeof config === 'function' ? [false, true] : [isProduction]; 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 opts of [resolvedConfig].flat()) { const entries = []; for (const loader of opts.module?.rules?.flatMap(resolveRuleSetDependencies) ?? []) { inputs.add(toDeferResolve(loader.replace(/\?.*/, ''))); } for (const plugin of opts?.plugins ?? []) { if (plugin && plugin.constructor.name === 'ProvidePlugin') { const providePluginInstance = plugin; if (providePluginInstance.definitions) { for (const values of Object.values(providePluginInstance.definitions)) { const specifier = typeof values === 'string' ? values : values[0]; inputs.add(toDeferResolve(specifier)); } } } } if (typeof opts.entry === 'string') entries.push(opts.entry); else if (Array.isArray(opts.entry)) entries.push(...opts.entry); else if (typeof opts.entry === 'object') { for (const entry of Object.values(opts.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)) { const dir = opts.context ? opts.context : cwd; const input = isProduction ? toDeferResolveProductionEntry(entry, { dir }) : toDeferResolveEntry(entry, { dir }); inputs.add(input); } else { inputs.add(toDeferResolve(entry)); } } const processAlias = (aliases) => { const addStar = (value) => (value.endsWith('*') ? value : join(value, '*').replace(/\/\*\*$/, '/*')); for (const [alias, value] of Object.entries(aliases)) { if (!value) continue; const prefixes = Array.isArray(value) ? value : [value]; if (alias.endsWith('$')) { inputs.add(toAlias(alias.slice(0, -1), prefixes)); } else { if (alias.length > 1) inputs.add(toAlias(alias, prefixes)); for (const prefix of prefixes) { inputs.add(toAlias(addStar(alias), [addStar(toAbsolute(prefix, options.configFileDir))])); } } } }; if (opts.resolve?.alias) { processAlias(opts.resolve.alias); } if (opts.resolveLoader?.alias) { processAlias(opts.resolveLoader.alias); } } } return Array.from(inputs); }; const resolveConfig = async (localConfig, options) => { const { manifest } = options; const inputs = await findWebpackDependenciesFromConfig(localConfig, options); 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(id => toDependency(id))]); }; const args = { binaries: ['webpack', 'webpack-dev-server'], config: true, }; export default { title, enablers, isEnabled, config, resolveConfig, args, };