Import errors and duplicated packages usually come down to resolution settings.
When webpack meets import x from "something", its resolver (enhanced-resolve) decides which file that is. The resolve options shape that:
extensions: suffixes tried for extension-less imports, e.g.[".tsx", ".ts", ".js"]. Keep the list short; every entry costs file-system lookups.alias: map a prefix to a path:"@": path.resolve(__dirname, "src"). A trailing$makes an exact match (react$). Aliases also force a single copy of a package, e.g. pointingreactat one location in a monorepo. Mirror aliases intsconfig.jsonpathsfor TypeScript.modules: directories searched for bare imports, default["node_modules"].conditionNamesandmainFields: which package.jsonexportsconditions and legacy fields are honoured (browser,module,import,require...). The defaults depend ontarget.fallback: webpack 5 no longer polyfills Node built-ins likecryptoorbufferfor the browser. Either avoid them, provide polyfills (fallback: { buffer: require.resolve("buffer/") }), or disable withfalse.
resolve.fullySpecified relates to ESM packages, where relative imports must include the extension; errors like "fully specified" come from .mjs files or "type": "module" packages.
Debug with npx webpack --stats=detailed or by temporarily setting infrastructureLogging: { level: "verbose" }.
Example
// webpack.config.js (excerpt)
const path = require("node:path");
module.exports = {
resolve: {
extensions: [".tsx", ".ts", ".js"],
alias: {
"@": path.resolve(__dirname, "src"), // import "@/lib/api"
react$: path.resolve(__dirname, "node_modules/react"), // one copy in a monorepo
},
fallback: {
buffer: require.resolve("buffer/"), // polyfill a Node built-in for the browser
fs: false, // never in the browser
},
},
};
// tsconfig.json: "compilerOptions": { "paths": { "@/*": ["./src/*"] } }Remember
extensions,alias,modulessteer the resolver- webpack 5 does not polyfill Node built-ins: use
fallback - Mirror aliases in tsconfig paths
- Keep
extensionsshort for speed
Watch out for
- Expecting
require('crypto')to work in browser code as it did with webpack 4's automatic polyfills.