You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

搭建含TSX组件与CSS Modules的npm库:两类问题求助

问题描述

我要做一个包含React组件和函数、能在NextJS项目中导入的npm模块,碰到两个问题:

  1. 所有JS代码被打包到dist/index.js单个文件里,没法保留src的文件夹结构,只有d.ts文件维持原有结构;
  2. 用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编译配置:

  1. 修改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规范
  },
  // 其余原有配置不变
};
  1. 更新tsconfig.json,确保TypeScript编译保留文件结构:
    在compilerOptions中添加:
"declarationDir": "./dist",
"rootDir": "./src"
  1. 优化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实例或模块规范不匹配导致,解决步骤如下:

  1. 将react和react-dom改为peer依赖,避免本地链接时重复安装:
"dependencies": {},
"peerDependencies": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
},
"peerDependenciesMeta": {
  "react": { "optional": false },
  "react-dom": { "optional": false }
}
  1. 修改Webpack配置,排除react和react-dom的打包,使用宿主项目的实例:
module.exports = {
  // 其余配置不变
  externals: {
    react: "commonjs react",
    "react-dom": "commonjs react-dom"
  }
};
  1. 确保导出方式规范,比如在src/index.ts中:
export * from './components/testComponent';
export * from './components/testReturn';
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 00:01:09