Monorepo源码引用实践:基于Webpack插件的路径解析方案

前言

前段时间看了U佬的一篇文章,他在里面讲述了自己在实习过程中遇到的一个工程化需求的挑战,受到大佬的启发,所以自己也想试试去实现一下这个方案,奈何实习原因确实是比较忙,根本没有足够的时间静下心来去具体全面地分析过这个问题,导致自己的思路也是断断续续,此外,我觉得自己有些愚钝,作为一个工程化小白,其实并没有完全理解原作者的意图,所以我只能从中提取了一些重要的信息‼️来进行需求分析与实践,接下来不妨听听小编的拙见,如果有哪里做的好不好或者有更好的思路欢迎您在评论区发表您的高见,小编定会洗耳恭听👏

这是u佬的需求背景传送门: 说来惭愧,入职的第一个需求居然花了我两多个月?!😭

需求分析

下面我先总结一下原文大概的一个意图,让读者即使不去细看上面的传送门也可以理解小编想要做什么的是什么:

背景:

  • 目前有一个monorepo项目,其中SDK子项目通过alias被其他项目引入
  • alias路径冗长且无法模块化
  • 项目打包速度慢
  • 想更换monorepo源码引用方式和SDK子项目构建工具

问题分析:

  • 需要实现项目间的源码引用而非引用构建产物
  • 需要替换Rollup为更高效的构建工具
  • 需要实现路径解析以简化引用

解决思路:

  • 创建自定义webpack插件解析路径
  • 创建自定义构建工具替代Rollup(基于Rollup的轻量级构建工具x-build)
  • 配置TypeScript和package.json支持项目间引用

项目实现

项目结构设计

我创建了包含三个子项目的monorepo结构:

  • A项目:业务项目,依赖B-SDK
  • B-SDK:SDK项目,被A项目引用
  • x-build:自定义构建工具,替代Rollup

目录结构如下:

bash 复制代码
monorepo-project/
├── packages/
│   ├── a/                  # A项目
│   ├── b-sdk/              # B-SDK项目
│   └── x-build/            # 自定义构建工具
├── plugins/
│   └── monorepo-path-resolver-plugin.js # 路径解析插件
├── package.json            # 根项目配置
└── pnpm-workspace.yaml     # 工作区配置

接下来的步骤的话直接复制到自己的终端bash工作台就可以复现小编的实现步骤啦(tip:小编是利用 pnpm 对 workspace 的天然支持构建的 monorepo 项目,所以对 node 版本要求16之上)

步骤一:搭建基础Monorepo结构

创建项目目录

bash 复制代码
# 创建项目根目录
mkdir monorepo-project
cd monorepo-project

# 创建基本目录结构
mkdir -p packages/a packages/b-sdk packages/x-build plugins

配置根目录package.json

bash 复制代码
# 创建package.json文件
cat > package.json << 'EOF'
{
  "name": "monorepo-project",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "build": "pnpm run --filter="./packages/**" build",
    "dev": "pnpm run --filter="./packages/**" dev"
  },
  "devDependencies": {
    "typescript": "^4.9.5"
  }
}
EOF

创建pnpm工作区配置

bash 复制代码
# 创建pnpm-workspace.yaml
cat > pnpm-workspace.yaml << 'EOF'
packages:
  - 'packages/*'
EOF

步骤二:实现路径解析插件

创建Webpack插件

webpack 插件的编写可以参考这篇博客:干货!撸一个webpack插件(内含tapable详解+webpack流程)

下面小编直接贴出相对路径映射为绝对路径这个插件的实现代码:

js 复制代码
# 创建插件文件
cat > plugins/monorepo-path-resolver-plugin.js << 'EOF'
/**
 * Monorepo路径解析插件
 * 用于解析monorepo中子项目之间的源码引用
 */
class MonorepoPathResolverPlugin {
  /**
   * @param {Object} options - 插件配置项
   * @param {Object} options.packages - 包名到路径的映射,如: {'@monorepo/b-sdk': '/path/to/b-sdk/src'}
   */
  constructor(options) {
    this.options = options || {};
    this.packages = this.options.packages || {};
    
    // 打印配置的路径映射
    console.log('MonorepoPathResolverPlugin 路径映射配置:');
    for (const [packageName, packagePath] of Object.entries(this.packages)) {
      console.log(`  ${packageName} => ${packagePath} (${typeof packagePath})`);
    }
  }

  /**
   * 应用插件
   * @param {Object} compiler - webpack编译器实例
   */
  apply(compiler) {
    // 注册解析器钩子
    compiler.hooks.normalModuleFactory.tap('MonorepoPathResolverPlugin', (factory) => {
      // 覆盖默认的模块解析逻辑
      factory.hooks.beforeResolve.tap('MonorepoPathResolverPlugin', (data) => {
        if (!data.request) {
          return;
        }

        // 检查是否是需要处理的包
        for (const [packageName, packagePath] of Object.entries(this.packages)) {
          // 完全匹配包名
          if (data.request === packageName) {
            const originalRequest = data.request;
            // 将包名解析为绝对路径
            data.request = `${packagePath}/index`;
            console.log(`路径解析: ${originalRequest} => ${data.request}`);
            break;
          }
          
          // 匹配子路径, 例如: @monorepo/b-sdk/utils
          if (data.request.startsWith(`${packageName}/`)) {
            const subPath = data.request.slice(packageName.length + 1);
            const originalRequest = data.request;
            data.request = `${packagePath}/${subPath}`;
            console.log(`路径解析: ${originalRequest} => ${data.request}`);
            break;
          }
        }

        // 在bailing钩子中返回undefined或true表示继续处理
        return;
      });
    });
    
    // 输出插件信息
    compiler.hooks.compile.tap('MonorepoPathResolverPlugin', () => {
      console.log('MonorepoPathResolverPlugin: 正在解析monorepo包路径...');
      console.log('已配置的包:', Object.keys(this.packages));
    });
  }
}

module.exports = { MonorepoPathResolverPlugin };
EOF

步骤三:配置SDK项目(B-SDK)

创建B-SDK的package.json

bash 复制代码
cat > packages/b-sdk/package.json << 'EOF'
{
  "name": "@monorepo/b-sdk",
  "version": "1.0.0",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "files": ["dist"],
  "scripts": {
    "build": "x-build",
    "dev": "x-build --watch"
  },
  "devDependencies": {
    "typescript": "^4.9.5",
    "@monorepo/x-build": "workspace:*"
  }
}
EOF

创建B-SDK的TypeScript配置

bash 复制代码
cat > packages/b-sdk/tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}
EOF

创建B-SDK的自定义构建配置

js 复制代码
cat > packages/b-sdk/x-build.config.js << 'EOF'
/**
 * x-build 配置文件
 */
const path = require('path');

module.exports = {
  input: path.resolve(__dirname, 'src/index.ts'),
  output: path.resolve(__dirname, 'dist'),
  minify: process.env.NODE_ENV === 'production',
  // 其他自定义配置
  banner: '/* B-SDK v1.0.0 */'
};
EOF

创建B-SDK的源码文件

bash 复制代码
# 创建src目录
mkdir -p packages/b-sdk/src packages/b-sdk/dist

# 创建入口文件
cat > packages/b-sdk/src/index.ts << 'EOF'
// B-SDK入口文件
export * from './utils';
export * from './core';

export const version = '1.0.0';
EOF

# 创建工具函数模块
cat > packages/b-sdk/src/utils.ts << 'EOF'
/**
 * B-SDK工具函数
 */

/**
 * 格式化日期
 */
export function formatDate(date: Date): string {
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}

/**
 * 生成唯一ID
 */
export function generateId(): string {
  return Math.random().toString(36).substring(2) + Date.now().toString(36);
}
EOF

# 创建核心功能模块
cat > packages/b-sdk/src/core.ts << 'EOF'
/**
 * B-SDK核心功能
 */

export interface DataOptions {
  id?: string;
  timestamp?: number;
  type: string;
  payload: any;
}

export class SDKCore {
  private options: Record<string, any>;

  constructor(options: Record<string, any> = {}) {
    this.options = { ...options };
  }

  /**
   * 初始化SDK
   */
  init(): void {
    console.log('SDK初始化完成', this.options);
  }

  /**
   * 处理数据
   */
  processData(data: DataOptions): DataOptions {
    return {
      ...data,
      timestamp: data.timestamp || Date.now(),
      id: data.id || `data-${Date.now()}`
    };
  }
}
EOF

步骤四:配置业务项目(A)

创建A项目的package.json

bash 复制代码
cat > packages/a/package.json << 'EOF'
{
  "name": "@monorepo/a",
  "version": "1.0.0",
  "main": "dist/index.js",
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack --mode development --watch",
    "start": "node dist/index.js",
    "debug": "node src/debug.js"
  },
  "dependencies": {
    "@monorepo/b-sdk": "workspace:*"
  },
  "devDependencies": {
    "typescript": "^4.9.5",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1",
    "ts-loader": "^9.4.2"
  }
}
EOF

创建A项目的TypeScript配置

bash 复制代码
cat > packages/a/tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@monorepo/b-sdk": ["../b-sdk/src"],
      "@monorepo/b-sdk/*": ["../b-sdk/src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}
EOF

创建A项目的Webpack配置

bash 复制代码
cat > packages/a/webpack.config.js << 'EOF'
const path = require('path');
const { MonorepoPathResolverPlugin } = require('../../plugins/monorepo-path-resolver-plugin');

module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.js',
    library: {
      type: 'commonjs2'
    },
    clean: true
  },
  resolve: {
    extensions: ['.ts', '.js'],
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  module: {
    rules: [
      {
        test: /.ts$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    // 添加monorepo路径解析插件
    new MonorepoPathResolverPlugin({
      // 配置需要解析的包路径
      packages: {
        '@monorepo/b-sdk': path.resolve(__dirname, '../b-sdk/src')
      }
    })
  ],
  // 设置target为node以确保正确的输出
  target: 'node',
  // 输出更详细的错误信息
  stats: 'verbose',
  // 开发模式下生成sourcemap
  devtool: 'source-map'
};
EOF

创建A项目的源码文件

bash 复制代码
# 创建src目录
mkdir -p packages/a/src packages/a/dist

# 创建入口文件
cat > packages/a/src/index.ts << 'EOF'
// 项目A入口文件
import { SDKCore, formatDate, generateId } from '@monorepo/b-sdk';

// 初始化SDK
const sdk = new SDKCore({
  appId: 'app-a',
  debug: true
});

sdk.init();

// 使用SDK的工具函数
const today = formatDate(new Date());
console.log('今天日期:', today);

// 使用SDK的其他功能
const id = generateId();
console.log('生成的ID:', id);

// 处理数据
const result = sdk.processData({
  type: 'event',
  payload: {
    action: 'click',
    element: 'button'
  }
});

console.log('处理结果:', result);
EOF

# 创建调试脚本
cat > packages/a/src/debug.js << 'EOF'
// 简单的调试脚本,用于执行编译后的代码并捕获异常
try {
  console.log('正在执行A项目代码...');
  const compiledCode = require('../dist/index.js');
  console.log('执行成功,A项目代码已加载');
} catch (error) {
  console.error('执行A项目代码时出错:');
  console.error(error);
}
EOF

步骤五:实现自定义构建工具(x-build)

创建x-build的package.json

bash 复制代码
cat > packages/x-build/package.json << 'EOF'
{
  "name": "@monorepo/x-build",
  "version": "1.0.0",
  "main": "index.js",
  "bin": {
    "x-build": "./bin/x-build.js"
  },
  "dependencies": {
    "rollup": "^3.15.0",
    "rollup-plugin-typescript2": "^0.34.1",
    "rollup-plugin-terser": "^7.0.2",
    "commander": "^10.0.0",
    "chalk": "^4.1.2"
  }
}
EOF

创建x-build的入口文件

bash 复制代码
# 创建目录结构
mkdir -p packages/x-build/bin packages/x-build/lib

# 创建入口文件
cat > packages/x-build/index.js << 'EOF'
/**
 * x-build构建工具主入口
 */
const { build } = require('./lib/build');

module.exports = {
  build
};
EOF

创建x-build的命令行脚本

bash 复制代码
cat > packages/x-build/bin/x-build.js << 'EOF'
#!/usr/bin/env node

const { program } = require('commander');
const { build } = require('../lib/build');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');

// 获取当前工作目录
const cwd = process.cwd();

// 检查是否存在配置文件
let configPath = path.join(cwd, 'x-build.config.js');
let config = {};

if (fs.existsSync(configPath)) {
  config = require(configPath);
}

// 定义命令行参数
program
  .option('-w, --watch', '使用监听模式')
  .option('-m, --minify', '压缩代码')
  .option('-c, --config <path>', '指定配置文件路径')
  .option('-o, --output <path>', '指定输出目录')
  .parse(process.argv);

const options = program.opts();

// 如果指定了配置文件,则覆盖默认配置
if (options.config) {
  const customConfigPath = path.resolve(cwd, options.config);
  if (fs.existsSync(customConfigPath)) {
    config = require(customConfigPath);
  } else {
    console.error(chalk.red(`错误:找不到配置文件 ${customConfigPath}`));
    process.exit(1);
  }
}

// 合并命令行参数与配置文件
const buildOptions = {
  ...config,
  watch: options.watch || config.watch,
  minify: options.minify || config.minify,
  output: options.output || config.output
};

// 执行构建
console.log(chalk.blue(`x-build: 开始构建...`));

build(buildOptions)
  .then(() => {
    if (!buildOptions.watch) {
      console.log(chalk.green(`x-build: 构建成功完成!`));
    } else {
      console.log(chalk.yellow(`x-build: 监听文件变化中...`));
    }
  })
  .catch(err => {
    console.error(chalk.red(`x-build: 构建失败!`));
    console.error(err);
    process.exit(1);
  });
EOF

创建x-build的构建逻辑

bash 复制代码
cat > packages/x-build/lib/build.js << 'EOF'
/**
 * x-build 构建实现
 */
const path = require('path');
const fs = require('fs');
const { rollup } = require('rollup');
const typescript = require('rollup-plugin-typescript2');
const { terser } = require('rollup-plugin-terser');
const chalk = require('chalk');

/**
 * 执行构建
 * @param {Object} options - 构建选项
 * @returns {Promise} - 构建结果Promise
 */
async function build(options = {}) {
  // 获取当前工作目录
  const cwd = process.cwd();
  
  // 读取package.json
  const pkgPath = path.join(cwd, 'package.json');
  if (!fs.existsSync(pkgPath)) {
    throw new Error('找不到package.json文件');
  }
  
  const pkg = require(pkgPath);
  
  // 获取输入输出配置
  const inputFile = options.input || path.join(cwd, 'src/index.ts');
  const outputDir = options.output || path.join(cwd, 'dist');
  
  // 确保输出目录存在
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  // 读取tsconfig.json
  const tsconfigPath = path.join(cwd, 'tsconfig.json');
  const tsconfig = fs.existsSync(tsconfigPath) ? tsconfigPath : undefined;
  
  // 配置插件
  const plugins = [
    typescript({
      tsconfig,
      tsconfigOverride: {
        compilerOptions: {
          declaration: true,
          declarationDir: outputDir
        }
      }
    })
  ];
  
  // 如果需要压缩,添加terser插件
  if (options.minify) {
    plugins.push(terser());
  }
  
  try {
    // 创建bundle
    const bundle = await rollup({
      input: inputFile,
      plugins,
      external: Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.peerDependencies || {}))
    });
    
    // 输出CJS格式
    await bundle.write({
      file: path.join(outputDir, 'index.js'),
      format: 'cjs',
      exports: 'named',
      sourcemap: true
    });
    
    // 输出ESM格式
    await bundle.write({
      file: path.join(outputDir, 'index.esm.js'),
      format: 'esm',
      sourcemap: true
    });
    
    // 监听模式
    if (options.watch) {
      console.log(chalk.yellow('设置文件监听中...'));
      
      // 简单的文件监听实现
      const { watch } = require('fs');
      const srcDir = path.join(cwd, 'src');
      
      watch(srcDir, { recursive: true }, async (eventType, filename) => {
        if (filename) {
          console.log(chalk.blue(`检测到文件变更: ${filename}`));
          try {
            // 创建新bundle
            const newBundle = await rollup({
              input: inputFile,
              plugins,
              external: Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.peerDependencies || {}))
            });
            
            // 输出CJS格式
            await newBundle.write({
              file: path.join(outputDir, 'index.js'),
              format: 'cjs',
              exports: 'named',
              sourcemap: true
            });
            
            // 输出ESM格式
            await newBundle.write({
              file: path.join(outputDir, 'index.esm.js'),
              format: 'esm',
              sourcemap: true
            });
            
            console.log(chalk.green('重新构建成功'));
          } catch (error) {
            console.error(chalk.red('重新构建失败:'), error);
          }
        }
      });
    }
    
    return bundle;
  } catch (error) {
    console.error(chalk.red('构建过程中发生错误:'));
    console.error(error);
    throw error;
  }
}

module.exports = { build };
EOF

# 设置执行权限
chmod +x packages/x-build/bin/x-build.js

步骤六:创建项目说明文档

bash 复制代码
cat > README.md << 'EOF'
# Monorepo项目示例

这个项目演示了如何在monorepo中实现源码引用、构建工具切换和路径别名解析。

## 项目结构...
这个就省略掉了,可以去小编后面贴的源码地址查看哈

现在的代码总体结构如下:

python 复制代码
monorepo-project/
├── packages/
│   ├── a/                  # A项目
│   │   ├── src/            # 源代码
│   │   ├── dist/           # 构建输出
│   │   ├── package.json    # 项目配置
│   │   ├── tsconfig.json   # TypeScript配置 
│   │   └── webpack.config.js # Webpack配置
│   │
│   ├── b-sdk/              # B-SDK项目
│   │   ├── src/            # 源代码
│   │   ├── dist/           # 构建输出
│   │   ├── package.json    # 项目配置
│   │   ├── tsconfig.json   # TypeScript配置
│   │   └── x-build.config.js # 构建工具配置
│   │
│   └── x-build/            # 自定义构建工具
│       ├── bin/            # 命令行工具
│       ├── lib/            # 核心逻辑
│       └── package.json    # 项目配置
│
├── plugins/
│   └── monorepo-path-resolver-plugin.js # Webpack路径解析插件
│
├── package.json            # 根项目配置
└── pnpm-workspace.yaml     # 工作区配置

功能特性

  • Monorepo结构: 使用pnpm workspaces管理多包项目
  • 源码引用: A项目可以直接引用B-SDK的源代码,而不是构建后的产物
  • 路径解析: 使用自定义Webpack插件解析模块路径
  • 自定义构建工具: 使用x-build替代Rollup,提供更好的性能和扩展性

使用方法

安装依赖

bash 复制代码
pnpm install

构建所有项目

bash 复制代码
pnpm build

开发模式

bsah 复制代码
pnpm dev

运行A项目

bash 复制代码
cd packages/a
npm run build && npm run debug

运行结果分析

代码运行的正确性校验:

webpack插件功能校验:

转换前:

转换后:

鼠标悬浮后也可以看到效果(在项目A中引入的是绝对路径):

太好了!现在我们看到了完整的输出结果。A项目成功调用了B-SDK的功能:

  1. SDK初始化:SDK初始化完成 { appId: 'app-a', debug: true }
  2. 使用 formatDate 功能:今天日期: 2025-03-08
  3. 使用 generateId 功能:生成了唯一ID lkk5dvyui9rm7zu0dju
  4. 使用 processData 功能:成功处理了数据并返回了结果,包含时间戳和自动生成的ID

小编之前没有看到输出是因为webpack只会构建代码,但不会执行它。后来过添加debug.js脚本解决了这个问题,它负责加载并执行编译后的代码。现在我们的 monorepo 结构完全正常工作了,A项目成功地从源码级别引用了B-SDK,这正是我们最初的目标。这种方式比使用打包后的产物有以下优势:

  1. 在开发过程中可以实时看到B-SDK的更改反映到A项目中
  2. 不需要每次修改B-SDK后重新构建并安装
  3. 调试更加方便,可以直接追踪到B-SDK源码

到此,我们现在就有了一个完全可用的 monorepo 项目,它使用 webpack 插件和自定义构建工具实现了模块间的源码引用。

踩坑点分享

小编在实现过程中遇到一下主要的问题,简要分享一下~~

1. 找不到模块"./utils"或其相应的类型声明。

问题是在packages/b-sdk/src/index.ts文件中导入./utils./core模块时出现了TypeScript错误,提示找不到这些模块或其相应的类型声明。

虽然我们已经创建了这些文件(utils.ts和core.ts),但TypeScript编译器可能无法正确识别它们。可能的原因有:

  1. TypeScript配置问题 - 可能需要检查tsconfig.json配置
  2. 文件路径问题 - 确保文件路径正确
  3. 类型声明问题 - 可能需要添加或修改类型声明

让我们看一下现有的tsconfig.json配置,然后提出修复方案:

packages/b-sdk/tsconfig.json文件中,我们有配置:

js 复制代码
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

最可能的原因是,在TypeScript转换过程中发生了问题,尤其是处理相对路径时。这里有几种解决方案:

  1. 确保moduleResolution设置正确
  2. 添加明确的文件扩展名
  3. 配置resolveJsonModule选项
  4. 修改导入语句格式

最简单的解决方案是在导入语句中添加文件扩展名。让我们修改index.ts文件。这个错误是TypeScript无法找到相对导入的模块。虽然文件确实存在,但TypeScript编译器需要额外配置来正确识别它们。

有几种简单的解决方案:

js 复制代码
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

配置已更新。这种方法的解决思路:

  1. 添加了resolveJsonModule来增强模块解析能力
  2. 修改了include模式为更明确的src/**/*.ts
  3. 添加了allowSyntheticDefaultImports以增强兼容性

这些更改能解决模块无法找到的问题。或者还可以尝试另一种解决方案,在导入语句中添加.js扩展名(即使文件是.ts,TypeScript在编译后会期望找到.js文件):

js 复制代码
export * from './utils.js';
export * from './core.js'

这两种方法应该都是解决TypeScript模块解析问题的常用方法☝️😁

找不到模块"@monorepo/b-sdk"或其相应的类型声明。

这个错误说明TypeScript找不到模块"@monorepo/b-sdk"。这个错误发生在项目A中,尝试导入B-SDK时出现的。

在我们的monorepo设置中,项目A依赖于项目B-SDK,通过workspace协议声明了依赖关系(在package.json中使用"@monorepo/b-sdk": "workspace:*")。

解决这个问题的方法是在项目A的tsconfig.json中添加模块声明映射,具体来说:

  1. 我们需要确保A项目的tsconfig.json正确配置了paths,指向B-SDK的源代码
  2. 可能需要添加一个类型声明文件来帮助TypeScript识别这个模块 让我们看看目前A项目的tsconfig.json配置:

我看到A项目的tsconfig.json中已经有了paths配置:

js 复制代码
"paths": {
  "@/*": ["src/*"],
  "@monorepo/b-sdk/*": ["../b-sdk/src/*"]
}

但是这个配置只解决了子路径导入,例如 import xxx from '@monorepo/b-sdk/utils',而不解决直接导入,例如 import { SDKCore } from '@monorepo/b-sdk'

我们需要添加一个不带 /* 的路径映射:在A项目中无法找到@monorepo/b-sdk模块,需要修改A项目的tsconfig.json路径映射配置:

js 复制代码
{
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@monorepo/b-sdk": ["../b-sdk/src"],
      "@monorepo/b-sdk/*": ["../b-sdk/src/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

问题是你需要为直接导入的包名添加路径映射。我刚刚添加了"@monorepo/b-sdk": ["../b-sdk/src"]配置,这样TypeScript就能识别直接从包名导入的情况。

原来的配置只处理了子路径导入(如@monorepo/b-sdk/utils),但缺少了根路径导入(@monorepo/b-sdk)的映射。

@monorepo/x-build is not in the npm registry, or you have no permission to fetch it.

这个错误是因为pnpm尝试从npm仓库获取@monorepo/x-build包,而不是使用本地工作区包。我们需要修改B-SDK项目的package.json:

修改如下:

结语

亲爱的读者们,还记得那些在 Monorepo 项目中被冗长引用路径和缓慢构建速度折磨得死去活来的日子吗?那种感觉就像你在高级餐厅点了一份龙虾,但服务员告诉你:"对不起,您需要先填写一份57页的申请表,然后等待3小时审核,接着还要自己去厨房告诉厨师怎么烹饪。" -- 这就是传统 Monorepo 项目的开发体验...但今天, 我们似乎找到了解救之道!! 快来锐评一下小编的 Monorepo 源码引用方案吧哈哈

仓库地址: webpack_plugin

相关推荐
Book_熬夜!4 分钟前
CSS—补充:CSS计数器、单位、@media媒体查询
前端·css·html·媒体
几度泥的菜花1 小时前
如何禁用移动端页面的多点触控和手势缩放
前端·javascript
狼性书生1 小时前
electron + vue3 + vite 渲染进程到主进程的双向通信
前端·javascript·electron
肥肠可耐的西西公主1 小时前
前端(AJAX)学习笔记(CLASS 4):进阶
前端·笔记·学习
拉不动的猪1 小时前
Node.js(Express)
前端·javascript·面试
Re.不晚1 小时前
Web前端开发——HTML基础下
前端·javascript·html
几何心凉2 小时前
如何处理前端表单验证,确保用户输入合法?
前端·css·前端框架
浪遏2 小时前
面试官😏: 讲一下事件循环 ,顺便做道题🤪
前端·面试
Joeysoda2 小时前
JavaEE进阶(2) Spring Web MVC: Session 和 Cookie
java·前端·网络·spring·java-ee
小周同学:3 小时前
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
前端·npm·node.js