前言
前面四个里程碑,我们从前端工程化打底,到实现 elpis-core 服务框架,再到 DSL 建站框架和动态组件扩展,整套能力都在同一个项目里跑通了。但如果换一个新项目也要用这套能力,总不能每次都复制粘贴代码。
这一步就来把整套 Elpis 能力抽离成独立的 npm 包,发布成可复用的 SDK。后续业务项目只需要引入依赖、写业务配置,就能快速搭建中后台系统,不用再重复造轮子。
一、本地联调:先用 npm link
正式发布前,可以先在本地用 npm link 做联调,不用发到远程仓库就能测试包的使用效果。
两步即可完成本地链接:
- 在 elpis 包的根目录执行命令,创建全局软链接
bash
npm link
- 在测试项目(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 层面,采用「默认 + 扩展」的合并策略:
- 包内置
createForm、editForm、detailPanel等基础组件 - 业务项目可以编写自己的自定义组件,通过配置文件暴露出来
- 包内读取时把两部分配置合并,schema-view 就能同时使用内置和业务组件
同时 DSL 的 model 配置路径调整到业务项目下,由业务方自己维护领域模型配置,框架只负责解析渲染。
三、发布与使用
发布
调整 package.json 里的包名、主入口文件和版本号,确认依赖都放在 dependencies 里之后,执行发布命令即可:
bash
npm publish
使用
业务项目中使用只需要三步:
- 安装 npm 包
- 按约定目录写业务代码(controller、页面、DSL 配置)
- 调用包暴露的启动方法和构建方法
总结
这次抽离发包的内容不算复杂,但很能体现框架设计的思路 ------把不变的沉淀进内核,把变化的留给扩展。
整个过程几个关键收获:
- SDK 化改造的核心是「边界拆分」:明确哪些是包内置的通用能力,哪些是业务方自定义的部分
- 路径问题是抽离最常见的坑,用
__dirname定位包内、process.cwd()定位业务端,基本就能解决 - 可选扩展要用空模块降级,不能强制业务方写所有配置
npm link是本地调试 SDK 的利器,不用发布就能验证效果
注:抖音「哲玄前端」,《大前端全栈实践课》