UNPKG

knip

Version:

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

114 lines (113 loc) 4.93 kB
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, };