搭建含TSX组件与CSS Modules的npm库:两类问题求助
问题描述
我要做一个包含React组件和函数、能在NextJS项目中导入的npm模块,碰到两个问题:
- 所有JS代码被打包到
dist/index.js单个文件里,没法保留src的文件夹结构,只有d.ts文件维持原有结构; - 用
yarn link本地链接这个库时,VS Code里导入函数没报错,但NextJS开发服务器提示函数是undefined。
当前项目结构
- src index.ts - components testComponent.module.css testComponent.tsx testReturn.ts
src/index.ts导出components目录下的所有内容
配置文件
tsconfig.json
{ "compilerOptions": { "jsx": "react-jsx", "target": "ESNext", "module": "CommonJS", "outDir": "dist", "sourceMap": true, "strict": true, "declaration": true, "esModuleInterop": true, "noEmit": false, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "lib": ["ESNext", "DOM", "DOM.Iterable"], "moduleResolution": "node", "plugins": [{ "name": "typescript-plugin-css-modules" }], "baseUrl": "./src" }, "include": ["src/**/*"] }
webpack.config.js
const path = require("path"); module.exports = { mode: process.env.NODE_ENV || "development", entry: { index: "./src/index.ts", }, output: { path: path.resolve(__dirname, "dist"), }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, loader: "ts-loader", options: { configFile: "tsconfig.json", }, }, { test: /\.css$/i, use: [ "style-loader", { loader: "css-loader", options: { modules: { localIdentName: "[name]__[local]__[hash:base64:5]", }, }, }, ], }, ], }, resolve: { extensions: [".tsx", ".ts", ".js"], } }
package.json
{ "name": "testmodule", "scripts": { "prebuild": "rimraf dist", "build": "webpack" }, "main": "dist/index.js", "files": [ "dist" ], "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", "@types/css-modules": "^1.0.3", "ts-loader": "^9.4.4", "typescript": "^5.2.2", "webpack-cli": "^5.1.4", "rimraf": "^5.0.4", "css-loader": "^6.8.1", "style-loader": "^3.3.3", "typescript-plugin-css-modules": "^5.0.1", "webpack": "^5.88.2" } }
解决方案
问题1:保留src文件夹结构,避免打包为单个文件
当前Webpack配置将所有入口合并为单个文件,要保留结构需调整为多入口输出,并同步TypeScript编译配置:
- 修改Webpack配置,自动遍历src下的所有ts/tsx文件作为入口,输出对应路径的文件:
const path = require("path"); const fs = require("fs"); // 遍历src目录生成多入口配置 const getEntries = () => { const entries = {}; const traverseDir = (dir) => { const files = fs.readdirSync(dir); files.forEach(file => { const fullPath = path.join(dir, file); const stats = fs.statSync(fullPath); if (stats.isDirectory()) { traverseDir(fullPath); } else if (/\.(ts|tsx)$/.test(file)) { const entryName = path.relative(path.resolve(__dirname, "src"), fullPath).replace(/\.(ts|tsx)$/, ""); entries[entryName] = fullPath; } }); }; traverseDir(path.resolve(__dirname, "src")); return entries; }; module.exports = { mode: process.env.NODE_ENV || "development", entry: getEntries(), output: { path: path.resolve(__dirname, "dist"), filename: "[name].js", // 按src路径生成对应文件 libraryTarget: "commonjs2", // 适配CommonJS规范 }, // 其余原有配置不变 };
- 更新tsconfig.json,确保TypeScript编译保留文件结构:
在compilerOptions中添加:
"declarationDir": "./dist", "rootDir": "./src"
- 优化package.json的导出配置,支持灵活导入:
"main": "dist/index.js", "types": "dist/index.d.ts", "exports": { ".": "./dist/index.js", "./components/*": "./dist/components/*.js" }
问题2:yarn link后NextJS提示函数undefined
该问题多由重复React实例或模块规范不匹配导致,解决步骤如下:
- 将react和react-dom改为peer依赖,避免本地链接时重复安装:
"dependencies": {}, "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "peerDependenciesMeta": { "react": { "optional": false }, "react-dom": { "optional": false } }
- 修改Webpack配置,排除react和react-dom的打包,使用宿主项目的实例:
module.exports = { // 其余配置不变 externals: { react: "commonjs react", "react-dom": "commonjs react-dom" } };
- 确保导出方式规范,比如在src/index.ts中:
export * from './components/testComponent'; export * from './components/testReturn';
- 在NextJS项目的
next.config.js中添加别名,强制使用项目本地的React:
const path = require('path'); module.exports = { webpack: (config) => { config.resolve.alias = { ...config.resolve.alias, react: path.resolve('./node_modules/react'), 'react-dom': path.resolve('./node_modules/react-dom') }; return config; } };
Rollup是否更适合这类场景?
是的,Rollup天生更适合构建npm库:
- Tree Shaking更彻底,生成的代码更精简;
- 配置简洁,默认支持ES模块,同时兼容CommonJS;
- 对TypeScript、React组件及CSS模块的支持更成熟,社区有大量适配插件(如
@rollup/plugin-typescript、rollup-plugin-postcss); - 更容易控制输出结构,单文件或多文件输出都很灵活。
切换到Rollup的核心思路和Webpack一致:配置入口、处理TS/TSX与CSS模块、排除peer依赖、输出对应结构的文件。
内容的提问来源于stack exchange,提问作者Matthijs Brouwer
相关产品推荐
相关产品推荐

