Elpis:抽离业务代码,发布NPM包

业务抽离、NPM 发布与开放能力设计

前言

Elpis 框架核心功能开发完成后,为实现"开箱即用"的复用价值,需完成两大关键动作:一是抽离业务代码、沉淀公共核心逻辑 ,避免框架与具体业务耦合;二是打包为 NPM 包 ,让用户通过 npm install 即可快速接入,无需手动配置基础依赖与构建流程。本文将详细拆解从本地调试、开放能力设计,到最终发布的完整流程。

在发布 NPM 包前,需先通过 npm link 实现本地项目与框架的联调,验证功能完整性。具体步骤如下:

1. 前置准备:NPM 账号与包名定义

  • 第一步:在 NPM 官网 注册账号,记录用户名(如 hashan-cn);
  • 第二步:定义包名格式为 @用户名/框架名 (符合 NPM 作用域包规范),本文示例包名为 @hashan-cn/elpis
  • 第三步:创建本地测试项目 elpis-demo,用于模拟用户实际使用场景。

2. 本地联调配置

  • 在 Elpis 框架根目录的 package.json 中,确认 name 字段为定义的包名("name": "@hashan-cn/elpis");

  • elpis-demo 项目中,执行以下命令建立本地关联:

    bash 复制代码
    # 将 Elpis 框架链接到本地 NPM 全局仓库
    cd 【Elpis 框架根目录】
    npm link
    
    # 在测试项目中关联框架包
    cd 【elpis-demo 根目录】
    npm link @hashan-cn/elpis
  • 关联成功后,elpis-demorequire('@hashan-cn/elpis') 会直接指向本地 Elpis 框架代码,修改框架后无需重新安装即可实时生效。

二、开放能力设计:让框架支持"用户扩展"

Elpis 框架拆分为三大核心板块,需为每个板块设计开放接口,确保用户既能复用框架核心能力,又能灵活扩展业务代码。

板块 1:Elpis-Core(Koa 封装的 BFF 层)

Elpis-Core 是基于 Koa 封装的简易 Egg.js 层,核心作用是提供 Node 服务能力。设计思路是**"框架初始化 + 用户业务挂载"**,既返回可操作的实例,又支持加载用户自定义模块。

1. 暴露启动接口:给用户返回实例

框架对外暴露 serverStart 方法,用户传入配置即可启动服务,并获取 Koa 实例(便于后续扩展中间件、监听端口等):

js 复制代码
// Elpis 框架入口文件(index.js)
const ElpisCore = require("./elpis-core");

module.exports = {
  /**
   * 启动 Elpis 服务
   * @param {Object} options - 项目配置(如项目名、首页路径)
   * @return {Object} app - Koa 实例(Elpis 增强版)
   */
  serverStart(options = {}) {
    const app = ElpisCore.start(options); // 内部初始化 Koa 实例、加载框架核心模块
    return app;
  }
};

// 用户使用示例(elpis-demo/index.js)
const { serverStart } = require('@hashan-cn/elpis');

// 启动服务并传入自定义配置
const app = serverStart({
  name: 'ElpisDemo', // 项目名
  homePage: '/view/project-list' // 首页路由
});
2. 支持用户业务模块加载

框架核心模块(controller/middleware/service 等)需与用户业务模块合并挂载到 Koa 实例。修改各 Loader 逻辑,同时扫描"框架目录"和"用户目录"

js 复制代码
// 以 Middleware Loader 为例(elpis-core/loader/middleware.js)
const glob = require("glob");
const path = require("path");
const { sep } = path;

// 统一处理文件挂载的工具函数
const handelFile = (file) => {
  // 【省略:读取文件、解析模块、挂载到 app.middleware 的逻辑】
};

module.exports = (app) => {
  // 1. 加载框架自带的中间件(elpis/app/middleware)
  const elpisMiddlewareDir = path.resolve(__dirname, `..${sep}..${sep}app${sep}middleware`);
  const elpisFileList = glob.sync(path.resolve(elpisMiddlewareDir, `.${sep}**${sep}**.js`));
  elpisFileList.forEach(file => handelFile(file));

  // 2. 加载用户业务的中间件(elpis-demo/app/middleware)
  const userMiddlewareDir = path.resolve(app.businessPath, `.${sep}middleware`); // app.businessPath 为用户项目 app 目录
  const userFileList = glob.sync(path.resolve(userMiddlewareDir, `.${sep}**${sep}**.js`));
  userFileList.forEach(file => handelFile(file));
};

通过该逻辑,app.middleware 会同时包含框架默认中间件和用户自定义中间件,其他模块(controller/service 等)同理。

板块 2:前端构建层(Webpack + Vue3)

前端层基于 Webpack 构建,核心作用是"读取 JSON-Schema 配置 → 渲染页面"。设计思路是**"暴露构建入口 + 支持 Webpack 配置扩展 + 合并用户代码"**。

1. 暴露前端构建接口

框架对外暴露 frontendBuild 方法,用户传入环境变量(local/production)即可触发对应环境的构建:

js 复制代码
// Elpis 框架前端构建入口(frontend/index.js)
const FEBuildDev = require('./app/webpack/dev.js'); // 开发环境构建逻辑
const FEBuildProd = require('./app/webpack/prod.js'); // 生产环境构建逻辑

module.exports = {
  /**
   * 编译前端工程
   * @param {string} paramsEnv - 环境变量(local:开发 / production:生产)
   */
  frontendBuild(paramsEnv) {
    const env = paramsEnv.trim(); // 处理 Windows 系统命令行参数空格问题
    if (env === 'local') {
      FEBuildDev(); // 启动开发环境(热更新、源码映射)
    } else if (env === 'production') {
      FEBuildProd(); // 启动生产环境(代码压缩、哈希命名)
    }
  }
};

// 用户使用示例(elpis-demo/frontend-build.js)
const { frontendBuild } = require('@hashan-cn/elpis');

// 从环境变量获取构建环境,触发构建
frontendBuild(process.env._ENV);
2. 支持 Webpack 配置扩展

框架 Webpack 配置分为 base(基础)、dev(开发)、prod(生产)三类,用户可通过自定义配置覆盖框架默认规则。核心逻辑是**"加载用户配置 + 合并到 base 配置"**(因 dev/prod 均基于 base 合并):

js 复制代码
// Elpis 框架 webpack.base.js
const merge = require('webpack-merge');
const path = require('path');

// 1. 框架默认基础配置
const baseConfig = {
  // 【省略:入口、输出、Loader、Plugin 等默认配置】
};

// 2. 加载用户自定义 Webpack 配置(elpis-demo/app/webpack.config.js)
let userWebpackConfig = {};
try {
  userWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`);
} catch (e) {
  console.log('未检测到用户 Webpack 配置,使用框架默认配置');
}

// 3. 合并配置:用户配置优先级高于框架默认配置
module.exports = merge.smart(baseConfig, userWebpackConfig);
3. 合并框架与用户前端代码

需将框架自带的前端组件(如基础布局、公共组件)与用户业务组件(如页面、自定义组件)一起编译,确保最终产物包含完整功能。修改入口扫描逻辑,同时读取"框架 pages 目录"和"用户 pages 目录"

js 复制代码
// Elpis 框架 webpack 入口生成逻辑
const glob = require("glob");
const path = require("path");
const { sep } = path;

// 工具函数:处理入口文件,生成 entry 和 HtmlWebpackPlugin 配置
const handleFile = (file, entryMap, pluginList) => {
  // 【省略:解析文件名、生成 entry 键值对、创建 HtmlWebpackPlugin 实例的逻辑】
};

// 1. 扫描框架前端入口(elpis/app/pages)
const elpisEntryList = path.resolve(__dirname, "../../pages/**/entry.*.js");
const elpisPageEntries = {};
const elpisHtmlPlugins = [];
glob.sync(elpisEntryList).forEach(file => {
  handleFile(file, elpisPageEntries, elpisHtmlPlugins);
});

// 2. 扫描用户前端入口(elpis-demo/app/pages)
const userEntryList = path.resolve(process.cwd(), "./app/pages/**/entry.*.js");
const userPageEntries = {};
const userHtmlPlugins = [];
glob.sync(userEntryList).forEach(file => {
  handleFile(file, userPageEntries, userHtmlPlugins);
});

// 3. 合并入口与插件配置
module.exports = {
  entry: Object.assign({}, elpisPageEntries, userPageEntries),
  plugins: [...elpisHtmlPlugins, ...userHtmlPlugins]
};
4. 解决依赖查找问题

用户项目 elpis-demo 构建时,Webpack 会优先从 elpis-demo/node_modules 查找依赖,但框架依赖(如 vue/webpack)实际安装在 Elpis 框架目录中,会导致"依赖找不到"报错。需在 Webpack 配置中指定依赖查找顺序:

js 复制代码
// Elpis 框架 webpack.base.js
module.exports = {
  resolve: {
    modules: [
      // 1. 优先查找框架的 node_modules(解决框架依赖找不到问题)
      path.resolve(__dirname, "../../../node_modules"),
      // 2. 再查找用户项目的 node_modules(用户自定义依赖)
      path.resolve(process.cwd(), "node_modules"),
      // 3. 默认查找路径
      "node_modules"
    ]
  }
};
5. 产物输出到用户目录

为方便用户直接使用构建产物,需将前端编译结果输出到 elpis-demo/app/public/dist 目录(用户可直接访问的静态资源目录):

js 复制代码
// Elpis 框架 webpack.prod.js(生产环境输出配置)
module.exports = {
  output: {
    filename: "js/[name]_[chunkhash:8].bundle.js", // 哈希命名防缓存
    path: path.join(process.cwd(), "./app/public/dist/prod"), // 输出到用户项目目录
    publicPath: "/dist/prod/", // 静态资源引用路径
    crossOriginLoading: "anonymous" // 允许跨域加载资源
  }
};

板块 3:JSON-Schema 配置层

该层是用户自定义业务的核心(如页面布局、表单规则),框架无需额外开发,只需约定用户配置文件存放路径并在前端构建时确保该目录被纳入扫描范围(即可实现"配置驱动页面渲染")。

三、业务抽离:明确框架与业务的边界

为确保框架轻量化、可复用,需严格区分"公共核心逻辑"与"具体业务代码",仅将公共部分沉淀到框架中:

类型 处理方式
框架公共逻辑 保留在 Elpis 框架中,如 Elpis-Core 核心 Loader、Webpack 基础配置、公共组件等
用户业务代码 从框架中抽离,复制到 elpis-demo 对应目录(如 app/controller/app/schema

抽离完成后,框架仅包含"可复用的能力",不绑定任何具体业务,用户可基于框架快速搭建新的项目。

四、NPM 包发布:从本地到线上

完成调试与业务抽离后,即可将 Elpis 框架发布到 NPM 仓库,供其他项目安装使用。

1. 发布前准备

  • 确认 package.json 关键字段配置正确:

    json 复制代码
    {
      "name": "@hashan-cn/elpis", // 包名(需与 NPM 账号一致)
      "version": "1.0.0", // 版本号(遵循 Semver 规范:主版本.次版本.修订号)
      "main": "index.js", // 入口文件
      "keywords": ["elpis", "koa", "webpack", "vue3"],
      "author": "你的名字",
      "license": "MIT" // 开源协议(如 MIT)
    }
  • 新增 .npmignore 文件,排除无需发布的文件(如测试文件、本地配置):

    bash 复制代码
    # 排除目录
    elpis-demo/
    tests/
    .git/
    # 排除文件
    .gitignore
    .env

2. 执行发布命令

bash 复制代码
# 1. 登录 NPM 账号(首次发布需登录,后续无需重复登录)
npm login
# 按提示输入用户名、密码、邮箱(注意:若使用 NPM 镜像,需先切回官方源:npm config set registry https://registry.npmjs.org/)

# 2. 发布包(作用域包需加 --access public,否则默认私有包)
npm publish --access public

3. 验证发布结果

  • 发布成功后,可在 NPM 官网搜索包名(@hashan-cn/elpis),确认包已上线;

  • 用户可通过以下命令安装使用:

    bash 复制代码
    npm install @hashan-cn/elpis --save

总结

Elpis 框架从"本地开发"到"NPM 发布"的核心逻辑,是 "沉淀公共能力 + 设计开放接口" :通过 npm link 实现本地调试,确保功能完整性;通过"实例暴露 + 目录扫描 + 配置合并"支持用户扩展;通过严格的业务抽离确保框架轻量化;最终通过 NPM 发布实现跨项目复用

相关推荐
quikai19812 小时前
python练习第六组
java·前端·python
用户47949283569152 小时前
0.1加0.2为什么不等于0.3-答不上来的都挂了
前端·javascript·面试
rit84324992 小时前
C#实现的远程控制系统
前端·javascript·c#
南山安2 小时前
React学习:Vite+React 基础架构分析
javascript·react.js·面试
诺斯贝克2 小时前
Unable to create converter for xxx.NetworkResponse<Auth> for method AuthService
前端·后端
listhi5202 小时前
针对燃油运输和车辆调度问题的蚁群算法MATLAB实现
前端·算法·matlab
渔_2 小时前
uni-app 页面传参总丢值?3 种方法稳如狗!
前端
快被玩坏了2 小时前
二次封装了个复杂的el-table表格
前端
用户93816912553602 小时前
在TypeScript中,可选属性(?)与null类型的区别
前端