webpack-dep-finder
Version:
A Webpack plugin and CLI tool for efficiently pinpointing and analyzing dependencies in your Webpack bundles using regex. Ideal for optimizing and streamlining the build process during iterative development, helping to detangle complex dependency trees qu
186 lines (164 loc) • 6.3 kB
JavaScript
const { program } = require("commander");
const path = require("path");
const fs = require("fs");
const glob = require("glob");
const WebpackDepFinder = require("../lib/plugin");
const tsNode = require("ts-node");
// Array of possible webpack config filenames
const configFilenames = [
"webpack.config.js",
"webpack.config.ts",
"webpack.config.dev.js",
"webpack.config.prod.js",
"webpackfile.js"
];
async function runWebpackDepFinder({
dependencyPattern,
haltOnMatch,
configPath,
showWebpackOutput,
entryPoint,
entryMatch
}) {
let pattern;
try {
pattern = new RegExp(dependencyPattern);
} catch (error) {
console.error("Error: Invalid regular expression provided for dependency-pattern.");
process.exit(1);
}
let webpackConfig = configPath
? loadWebpackConfig(path.resolve(process.cwd(), configPath))
: findAndLoadWebpackConfig();
if (!webpackConfig) {
console.error("Error: No valid Webpack configuration found.");
process.exit(1);
}
// Override Webpack's entry point if --entry-match or --entry-point is provided
if (entryMatch) {
const matchedEntry = findMatchingEntry(entryMatch);
if (matchedEntry) {
webpackConfig.entry = path.resolve(process.cwd(), matchedEntry);
console.log(`Using entry point from match: ${webpackConfig.entry}`);
} else {
console.error(`Error: No entry point matching "${entryMatch}" was found.`);
process.exit(1);
}
} else if (entryPoint) {
webpackConfig.entry = path.resolve(process.cwd(), entryPoint);
console.log(`Using entry point: ${webpackConfig.entry}`);
} else {
console.log(`Entry point module: ${webpackConfig.entry}`);
}
// Modify ts-loader settings to use `transpileOnly: true`
if (webpackConfig.module && webpackConfig.module.rules) {
webpackConfig.module.rules.forEach(rule => {
if (rule.use && rule.use.loader === "ts-loader") {
console.log("Overriding ts-loader to use transpileOnly: true");
rule.use.options = {
...rule.use.options,
transpileOnly: true
};
}
});
}
let Webpack;
const localWebpackPath = path.resolve(process.cwd(), "node_modules", "webpack");
if (fs.existsSync(localWebpackPath)) {
console.log("Using local Webpack from", localWebpackPath);
Webpack = require(localWebpackPath);
} else {
console.log("No local Webpack found, using global Webpack.");
try {
Webpack = require("webpack");
} catch (error) {
console.error("Error: Webpack is not installed globally or locally.");
process.exit(1);
}
}
const plugin = new WebpackDepFinder({
dependencyPattern: pattern,
bail: haltOnMatch,
squelchWebpackOutput: !showWebpackOutput
});
webpackConfig.plugins = webpackConfig.plugins || [];
webpackConfig.plugins.push(plugin);
const compiler = Webpack(webpackConfig);
compiler.run((err, stats) => {
if (err) {
console.error("Error during the Webpack compilation:", err);
process.exit(1);
}
if (stats.hasErrors()) {
console.error("Webpack compilation errors:", stats.toJson().errors);
process.exit(1);
}
console.log("Webpack compilation completed successfully.");
process.exit(0);
});
}
function loadWebpackConfig(configPath) {
try {
tsNode.register({ transpileOnly: true });
let config = require(configPath);
if (typeof config === "function") config = config({});
if (Array.isArray(config)) config = config[0];
return config;
} catch (error) {
console.error(`Error loading Webpack config file at ${configPath}:`, error);
return null;
}
}
function findAndLoadWebpackConfig() {
for (const filename of configFilenames) {
const resolvedPath = path.resolve(process.cwd(), filename);
if (fs.existsSync(resolvedPath)) {
console.log(`Found Webpack config file: ${resolvedPath}`);
return loadWebpackConfig(resolvedPath);
}
}
return null;
}
function findMatchingEntry(entryMatch) {
// Convert entryMatch to a case-insensitive regex if it's not already a regex
const regex = new RegExp(entryMatch, "i");
// Search for files that match the given pattern in the source folder
const matches = glob.sync("**/*", {
cwd: path.resolve(process.cwd(), "src"), // Search within src directory
nodir: true // Ensure we only get files, not directories
});
// Find the first matching file based on the regex
return matches.find(file => regex.test(file));
}
const getVersion = () => {
const packageJsonPath = path.resolve(__dirname, "../package.json");
const packageJson = fs.readFileSync(packageJsonPath, "utf-8");
const pkg = JSON.parse(packageJson);
return pkg.version;
};
// CLI Command Parsing
if (require.main === module) {
program
.requiredOption(
"-d, --dependency-pattern <pattern>",
"Regex pattern to match the resource path/filename to locate."
)
.option("--no-halt-on-match", "Continue searching even after the dependency is found.")
.option("--show-webpack-output", "Display Webpack's build output.")
.option("-c, --config <path>", "Path to the Webpack configuration file.")
.option("--entry-point <path>", "Specify an entry point to begin the search from.")
.option("--entry-match <pattern>", "Specify a regex or partial string to match the entry point filename.")
.version(getVersion())
.parse(process.argv);
const options = program.opts();
runWebpackDepFinder({
dependencyPattern: options.dependencyPattern,
haltOnMatch: !options.noHaltOnMatch,
configPath: options.config,
showWebpackOutput: options.showWebpackOutput,
entryPoint: options.entryPoint,
entryMatch: options.entryMatch
});
}
module.exports = { runWebpackDepFinder };