Elpis npm 包抽离总结

本文档总结将 elpis 框架抽离为 npm 包过程中的核心难点与解决方案


核心难点

1. 路径解析的双重性挑战 ⭐⭐⭐

问题:npm 包内代码需要同时访问包内资源和业务项目资源,两类路径的解析方式完全不同。

核心区别

路径类型 使用变量 指向位置 特点
包内路径 __dirname npm 包安装目录(如 node_modules/@david-yjy/elpis/... 固定不变
业务路径 process.cwd() 执行命令时的当前工作目录(业务项目根目录) 动态变化

代码示例

javascript 复制代码
// webpack.base.js

// ✅ 包内路径:使用 __dirname
const elpisEntryList = path.resolve(__dirname, '../../pages/**/entry.*.js');

// ✅ 业务路径:使用 process.cwd()
const businessEntryList = path.resolve(process.cwd(), './app/pages/**/entry.*.js');

常见卡点

javascript 复制代码
// ❌ 错误:混淆两种路径类型
const businessConfig = require(path.resolve(__dirname, './app/config.js'));

// ✅ 正确:明确区分
const businessConfig = require(path.resolve(process.cwd(), './app/config.js'));

学习要点

  • __dirname = 模块文件所在目录(包内,固定)
  • process.cwd() = 进程当前工作目录(业务项目,动态)
  • 永远不要假设业务项目路径相对于包的位置

2. Loader 解析的路径问题 ⭐⭐⭐

问题:直接使用字符串路径配置 loader 会导致路径找不到或版本冲突。

解决方案 :使用 require.resolve() 确保从包内解析 loader。

代码对比

javascript 复制代码
// ❌ 错误:可能找不到或版本不对
module: {
  rules: [{
    test: /\.vue$/,
    use: 'vue-loader'
  }]
}

// ✅ 正确:使用 require.resolve() 从包内解析
module: {
  rules: [{
    test: /\.vue$/,
    use: {
      loader: require.resolve('vue-loader'),  // 确保版本一致
    }
  }]
}

为什么这样做?

  • 版本一致性:确保使用包内定义的 loader 版本
  • 路径可靠性:无论在什么环境下都能找到正确的 loader
  • 隔离性:包内依赖不会影响业务项目

3. 业务扩展点的可选性设计 ⭐⭐

问题 :框架需要提供扩展点让业务项目自定义配置,但这些配置必须是可选的,缺失时不能报错。

解决方案 :使用 fs.existsSync() 检查 + 空模块降级。

代码示例

javascript 复制代码
// webpack.base.js

resolve: {
  alias: (() => {
    const aliasMap = {};
    const blankModulePath = path.resolve(__dirname, '../libs/blank.js');

    // 检查业务配置文件是否存在
    const businessConfig = path.resolve(process.cwd(), './app/pages/dashboard/root.js');

    // ✅ 关键:存在用业务配置,不存在用空模块
    aliasMap['$businessDashboardRouterConfig'] =
      fs.existsSync(businessConfig)
        ? businessConfig      // 存在:使用业务配置
        : blankModulePath;    // 不存在:使用空模块

    return aliasMap;
  })()
}

空模块 (blank.js):

javascript 复制代码
module.exports = {}  // 确保业务代码可以安全 require

设计模式

  1. 检测fs.existsSync() 检查文件是否存在
  2. 降级:不存在时提供默认实现(空模块)
  3. 透明:通过 webpack alias,业务代码无需关心配置来源

4. 配置合并与容错加载 ⭐⭐

问题:业务项目可能想要自定义 webpack 配置,但这个配置应该是可选的。

解决方案:使用 try-catch 容错 + webpack-merge 合并。

代码示例

javascript 复制代码
// webpack.base.js

// ✅ 容错加载业务配置
let businessWebpackConfig = {};
try {
  businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`);
} catch (error) {
  // 静默处理:业务配置不存在是正常的
  console.log('未找到业务 webpack 配置');
}

// ✅ 使用 webpack-merge 的 smart 策略合并
module.exports = merge.smart(
  baseConfig,        // 框架基础配置
  businessWebpackConfig  // 业务配置(可选,会覆盖基础配置)
);

关键点

  • ✅ 业务配置缺失不影响框架功能
  • ✅ 业务配置会智能合并(数组合并,对象覆盖)
  • ✅ 降低使用门槛,渐进增强

关键技术点总结

路径解析的核心原则

场景 使用变量 原因
包内资源路径 __dirname 指向包安装位置,固定不变
业务资源路径 process.cwd() 指向执行目录,动态变化
Loader/插件路径 require.resolve() 确保从包内解析,版本一致

容错设计模式

javascript 复制代码
// 模式1:可选配置检查 + 空模块降级
const config = fs.existsSync(configPath) ? configPath : blankModulePath;

// 模式2:try-catch 容错
let config = {};
try {
  config = require(configPath);
} catch (error) {
  // 静默处理
}

配置合并策略

javascript 复制代码
// 使用 webpack-merge 的 smart 策略
const finalConfig = merge.smart(baseConfig, businessConfig);
// - 对象属性:后面的覆盖前面的
// - 数组:会合并去重(如 plugins、rules)

最佳实践

1. 路径处理规范

javascript 复制代码
// ✅ 推荐:使用辅助函数明确区分
function getPackagePath(...paths) {
  return path.resolve(__dirname, '../../', ...paths);
}

function getProjectPath(...paths) {
  return path.resolve(process.cwd(), ...paths);
}

2. 依赖管理

  • 框架包 :构建依赖放在 dependencies,确保版本一致
  • 业务项目:只需安装框架包,依赖由包提供

总结

将框架抽离为 npm 包的核心挑战在于路径上下文的管理。通过合理使用:

  1. __dirname - 访问包内资源(固定)
  2. process.cwd() - 访问业务项目资源(动态)
  3. require.resolve() - 解析包内依赖(版本一致)
  4. 容错设计 - 可选配置优雅降级
相关推荐
user20585561518131 天前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州1 天前
CSS aspect-ratio 属性完全指南
前端
Pedantic1 天前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘1 天前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆1 天前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师1 天前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆1 天前
VSCode自动格式化三要素
前端
爱勇宝1 天前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen1 天前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程