【学习】Elpis 抽离与发布 npm 包

前言

前面四个里程碑,我们从前端工程化打底,到实现 elpis-core 服务框架,再到 DSL 建站框架和动态组件扩展,整套能力都在同一个项目里跑通了。但如果换一个新项目也要用这套能力,总不能每次都复制粘贴代码。

这一步就来把整套 Elpis 能力抽离成独立的 npm 包,发布成可复用的 SDK。后续业务项目只需要引入依赖、写业务配置,就能快速搭建中后台系统,不用再重复造轮子。

正式发布前,可以先在本地用 npm link 做联调,不用发到远程仓库就能测试包的使用效果。

两步即可完成本地链接:

  1. 在 elpis 包的根目录执行命令,创建全局软链接

bash

复制代码
npm link
  1. 在测试项目(elpis-demo)中,把包链接进 node_modules

bash

复制代码
npm link elpis

这样测试项目里的 require('elpis') 就会直接指向本地的包源码,改完代码实时生效,调试非常方便。

二、三层核心抽离改造

抽离的核心思路很简单:通用能力留在包里,业务配置留给使用方。我们分三层来改造。

1. elpis-core:loader 双目录加载

原来的 loader 只会读取业务项目目录下的文件。抽离成包之后,要先加载包内置的默认能力,再加载业务项目的自定义内容,两者合并后挂载到 app 实例上。

以 controller loader 为例,改造后会先后读取两个目录:

javascript

运行

复制代码
module.exports = (app) => {
  const controllers = {};

  // 1. 加载包内置的 controller
  const elpisControllerPath = path.resolve(__dirname, '../../app/controller');
  glob.sync(path.resolve(elpisControllerPath, '**/*.js')).forEach(file => {
    handleFile(file);
  });

  // 2. 加载业务项目的 controller
  const businessControllerPath = path.resolve(app.businessPath, './controller');
  glob.sync(path.resolve(businessControllerPath, '**/*.js')).forEach(file => {
    handleFile(file);
  });

  // 统一处理文件,解析路径并挂载
  function handleFile(file) {
    let name = path.resolve(file);
    name = name.substring(name.lastIndexOf('controller/') + 'controller/'.length, name.lastIndexOf('.'));
    name = name.replace(/[_-]([a-z])/gi, (s) => s.substring(1).toUpperCase());

    let temController = controllers;
    const names = name.split('/');
    for (let i = 0, len = names.length; i < len; i++) {
      if (i === len - 1) {
        const ControllerModel = require(path.resolve(file))(app);
        temController[names[i]] = new ControllerModel();
      } else {
        if (!temController[names[i]]) temController[names[i]] = {};
        temController = temController[names[i]];
      }
    }
  }

  app.controller = controllers;
};

最后在包的入口文件 index.js 里暴露 serverStart 启动方法,业务项目引入后直接调用就能启动服务。

2. webpack 构建层:路径适配与能力暴露

前端构建部分的改造,核心是解决「路径定位」问题 ------ 原来都在项目根目录下找,现在要区分包内资源和业务资源。

① 暴露构建方法把开发服务和生产打包封装成方法对外暴露:

javascript

运行

复制代码
const FEBuildDev = require('./app/webpack/dev.js');
const FEBuildProd = require('./app/webpack/prod.js');

module.exports = {
  // 前端构建方法,传入环境切换开发/生产
  frontedBuild(env) {
    if (env === 'local') FEBuildDev();
    else if (env === 'production') FEBuildProd();
  }
};

② 路径与 loader 调整

  • 所有包内资源的路径别名,都用 __dirname 指向包内目录,比如 $elpisPages: path.resolve(__dirname, '../../pages')
  • loader 用 require.resolve('babel-loader') 写法,强制从包的依赖里查找,不依赖业务项目安装

③ 支持业务自定义配置 尝试读取业务项目根目录下的 webpack.config.js,和包内默认配置做智能合并,满足个性化需求。

④ 空模块降级处理 像业务组件扩展、路由扩展这些都是可选配置,如果业务项目没写对应文件,直接打包会报错。我们用 fs.existsSync 判断文件是否存在,不存在就指向一个空模块文件:

javascript

运行

复制代码
const businessComponentConfig = path.resolve(process.cwd(), './app/pages/dashboard/schema-view/components/component-config.js');
aliasMap['$businessComponentConfig'] = fs.existsSync(businessComponentConfig)
  ? businessComponentConfig
  : blankModulePath;

3. DSL 与动态组件:支持业务扩展

组件和 DSL 层面,采用「默认 + 扩展」的合并策略:

  • 包内置 createFormeditFormdetailPanel 等基础组件
  • 业务项目可以编写自己的自定义组件,通过配置文件暴露出来
  • 包内读取时把两部分配置合并,schema-view 就能同时使用内置和业务组件

同时 DSL 的 model 配置路径调整到业务项目下,由业务方自己维护领域模型配置,框架只负责解析渲染。

三、发布与使用

发布

调整 package.json 里的包名、主入口文件和版本号,确认依赖都放在 dependencies 里之后,执行发布命令即可:

bash

复制代码
npm publish

使用

业务项目中使用只需要三步:

  1. 安装 npm 包
  2. 按约定目录写业务代码(controller、页面、DSL 配置)
  3. 调用包暴露的启动方法和构建方法

总结

这次抽离发包的内容不算复杂,但很能体现框架设计的思路 ------把不变的沉淀进内核,把变化的留给扩展

整个过程几个关键收获:

  1. SDK 化改造的核心是「边界拆分」:明确哪些是包内置的通用能力,哪些是业务方自定义的部分
  2. 路径问题是抽离最常见的坑,用 __dirname 定位包内、process.cwd() 定位业务端,基本就能解决
  3. 可选扩展要用空模块降级,不能强制业务方写所有配置
  4. npm link 是本地调试 SDK 的利器,不用发布就能验证效果

注:抖音「哲玄前端」,《大前端全栈实践课》

相关推荐
PedroQue991 小时前
0.5.0重磅发布:动画插件+H5体验全面优化
前端·uni-app
金虹桥程序员1 小时前
【动画】 📚 CSS 动画与 GPU 合成层优化全链路指南
前端·面试
程序员老赵1 小时前
Docker 部署填鸭表单完整教程:搭建私有化问卷与表单收集平台
前端·docker·开源
无限压榨切图仔2 小时前
向量库能搜到内容,为什么还不算学会 RAG
前端·agent
mmsx2 小时前
MapLibre 实战 11|用户说"我的地块丢了":一个 sealed class 图层模型,和四个让我重构三版的坑
android·前端·开源
xqchen2 小时前
代码差异可视化方案选型:diff2html 实战指南
前端
名字还没想好☜2 小时前
用 Zustand 做 React 全局状态管理:告别 Context 重渲染,3 个实战模式与持久化
前端
IMPYLH2 小时前
HTML 的 <section> 元素
前端·html
梦帮科技2 小时前
Next.js 16 模块化单体实战:App Router、领域模块与 Route Handler 分层
css·数据结构·链表·正则表达式·node.js·json·html5