UNPKG

knip

Version:

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

126 lines (125 loc) 5.5 kB
import { compact } from '../../util/array.js'; import { toDeferResolve, toDeferResolveEntry, toDeferResolveProductionEntry, toDependency, toDevDependency, } from '../../util/input.js'; import { isInternal } 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(/\?.*/, ''))); } for (const plugin of options?.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 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 dir = options.context ? options.context : cwd; const input = options.mode === 'development' ? toDeferResolveEntry(entry, { dir }) : toDeferResolveProductionEntry(entry, { dir }); inputs.add(input); } } } } 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, };