Elpis 框架 npm 包抽象封装并发布完整指南
引用参考:抖音"哲玄前端"《大前端全栈实践》
前言
Elpis 是一个基于 Koa2 的全栈开发框架,它集成了前端工程化构建和后端服务的完整解决方案。本文将详细介绍如何将这个框架抽象封装成 npm 包并发布,让其他开发者可以通过简单的安装和配置快速搭建项目。
框架的核心设计理念是:
- 约定优于配置:通过统一的目录结构和命名规范,减少配置复杂度
- 插件化架构:支持业务项目的自定义扩展
- 工程化集成:内置完整的前端构建流程
- 开箱即用:提供完整的基础功能和最佳实践
1、package.json 配置
1.1 基础信息配置
json
{
"name": "@heartvital/heart-elpis",
"version": "1.0.0",
"description": "基于Koa2的全栈开发框架",
"main": "index.js",
"scripts": {
"lint": "eslint --quiet --ext js,vue .",
"test": "cross-env _ENV=local mocha 'test/**/*.js'"
},
"repository": {
"type": "git",
"url": "https://git.code.tencent.com/heart_/heart-elpis.git"
},
"author": "heart",
"license": "ISC"
}
关键配置说明:
- name 字段 :使用
@npm帐号/elpis
的形式,方便搜索识别,支持 npm 组织范围包管理 - main 字段 :指定入口文件为
index.js
,这是框架对外暴露 API 的核心文件 - 版本管理:采用语义化版本控制,确保版本更新的规范性
1.2 依赖管理策略
dependencies(运行时依赖):
json
{
"dependencies": {
"koa": "2.7.0",
"koa-router": "^7.4.0",
"koa-bodyparser": "^4.2.1",
"vue": "^3.3.4",
"webpack": "^5.88.1",
"babel-loader": "^8.0.4",
"vue-loader": "^17.2.2"
}
}
devDependencies(开发时依赖):
json
{
"devDependencies": {
"eslint": "^7.32.0",
"mocha": "^6.1.4",
"supertest": "^4.0.2"
}
}
1.3 框架能力暴露
在index.js
中定义框架的核心能力:
javascript
module.exports = {
// 服务端启动
serverStart(option = {}) {
const app = ElpisCore.start(option)
return app
},
// 前端构建
frontendBuild(env) {
if (env === 'local') {
FEBuildDev()
} else if (env === 'production') {
FEBuildProd()
}
},
// 基础类暴露
Controller: {
Base: require('./app/controller/base.js'),
},
Service: {
Base: require('./app/service/base.js'),
},
}
2、elpis-core 加载器的配置
2.1 路径管理策略
框架采用双路径管理策略,既支持框架内部资源,也支持业务项目资源:
javascript
// elpis-core/index.js
const app = new Koa()
// 项目基础路径(业务项目根目录)
app.baseDir = process.cwd()
// 业务文件路径
app.businessPath = path.resolve(app.baseDir, `./app`)
// 框架内部路径
const elpisPath = path.resolve(__dirname, '../app')
路径获取方式:
path.resolve(__dirname)
:获取框架内部文件的绝对路径path.resolve(process.cwd())
:获取业务项目的当前工作目录
2.2 加载器工作流程
javascript
// 加载器执行顺序
middlewareLoader(app) // 中间件加载
extendLoader(app) // 扩展功能加载
configLoader(app) // 配置加载
serviceLoader(app) // 服务层加载
controllerLoader(app) // 控制器加载
routerSchemaLoader(app) // 路由模式加载
routerLoader(app) // 路由加载
2.3 Controller 加载器实现
javascript
// elpis-core/loader/controller.js
module.exports = (app) => {
const controller = {}
// 1. 加载框架内部controller
const elpisControllerPath = path.resolve(__dirname, '../app/controller')
const elpisFileList = glob.sync(
path.resolve(elpisControllerPath, './**/*.js')
)
elpisFileList.forEach((file) => handleFile(file))
// 2. 加载业务项目controller
const businessControllerPath = path.resolve(app.businessPath, './controller')
const businessFileList = glob.sync(
path.resolve(businessControllerPath, './**/*.js')
)
businessFileList.forEach((file) => handleFile(file))
// 3. 文件处理逻辑
function handleFile(file) {
// 提取文件名并转换为驼峰命名
let name = extractControllerName(file)
// 动态加载并实例化Controller
const ControllerModule = require(path.resolve(file))(app)
mountController(controller, name, new ControllerModule())
}
app.controller = controller
}
3、frontendBuild 前端构建
3.1 构建环境区分
javascript
// app/webpack/dev.js - 开发环境
module.exports = () => {
const {
webpackConfig,
DEV_SERVER_CONFIG,
} = require('./config/webpack.dev.js')
const app = express()
const compiler = webpack(webpackConfig)
// 开发中间件配置
app.use(
devMiddleware(compiler, {
writeToDisk: (filePath) => filePath.endsWith('.tpl'),
publicPath: webpackConfig.output.publicPath,
headers: {
'Access-Control-Allow-Origin': '*',
},
})
)
// 热更新中间件
app.use(
hotMiddleware(compiler, {
path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
log: () => {},
})
)
}
3.2 webpack 基础配置
javascript
// app/webpack/config/webpack.base.js
const businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`)
module.exports = merge.smart(
{
// 入口动态构建
entry: Object.assign({}, elpisPageEntries, businessPageEntries),
// 模块解析规则
module: {
rules: [
{
test: /\.vue$/,
use: { loader: require.resolve('vue-loader') },
},
{
test: /\.js$/,
include: [
path.resolve(__dirname, '../../pages'),
path.resolve(process.cwd(), './app/pages'),
],
use: { loader: require.resolve('babel-loader') },
},
],
},
// 路径别名配置
resolve: {
alias: {
$elpisPages: path.resolve(__dirname, '../../pages'),
$elpisCommon: path.resolve(__dirname, '../../pages/common'),
$elpisCurl: path.resolve(__dirname, '../../pages/common/curl.js'),
},
},
},
businessWebpackConfig
)
3.3 require.resolve 的使用
使用require.resolve('vue-loader')
确保从框架包中获取 loader,而不是从业务项目的 node_modules 查找:
javascript
// 错误方式
loader: 'vue-loader'
// 正确方式
loader: require.resolve('vue-loader')
3.4 业务配置合并
javascript
// 加载业务webpack配置
let businessWebpackConfig = {}
try {
businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`)
} catch (e) {
console.log('未找到业务webpack配置文件')
}
// 使用webpack-merge进行智能合并
module.exports = merge.smart(elpisBaseConfig, businessWebpackConfig)
4、业务路由的配置
4.1 路由入口文件
javascript
// app/pages/dashboard/entry.dashboard.js
import boot from '$elpisPages/boot.js'
import dashboard from './dashboard.vue'
const routes = []
// 头部菜单路由
routes.push({
path: '/view/dashboard/iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue'),
})
// 侧边栏菜单路由
routes.push({
path: '/view/dashboard/sider',
component: () => import('./complex-view/sider-view/sider-view.vue'),
children: [
{
path: 'iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue'),
},
],
})
// 启动应用
boot(dashboard, { routes })
4.2 业务路由配置加载
业务项目可以通过创建$businessDashboardRouterConfig
文件来自定义路由:
javascript
// 业务项目 app/pages/dashboard/business-router.js
export default {
headerRoutes: [
{
path: '/business/custom',
component: () => import('./custom-view.vue'),
},
],
siderRoutes: [
{
path: 'business-module',
component: () => import('./business-module.vue'),
},
],
}
框架会自动合并业务路由配置:
javascript
// 框架内部路由合并逻辑
let businessRouterConfig = {}
try {
businessRouterConfig = require(`${process.cwd()}/app/pages/dashboard/business-router.js`)
} catch (e) {}
const finalRoutes = [
...frameworkRoutes,
...(businessRouterConfig.headerRoutes || []),
...(businessRouterConfig.siderRoutes || []),
]
5、Controller、Service 服务端基础
5.1 Controller 基础类
javascript
// app/controller/base.js
module.exports = (app) => {
return class BaseController {
constructor() {
this.app = app
this.config = app.config
}
// 成功响应统一格式
success(ctx, data = {}, metadata = {}) {
ctx.status = 200
ctx.body = {
success: true,
data,
metadata,
}
}
// 失败响应统一格式
fail(ctx, message, code) {
ctx.body = {
success: false,
message,
code,
}
}
}
}
5.2 Service 基础类
javascript
// app/service/base.js
const superAgent = require('superagent')
module.exports = (app) => {
return class BaseService {
constructor() {
this.app = app
this.config = app.config
this.curl = superAgent
}
// 可以在这里添加通用的服务方法
async request(url, options = {}) {
return await this.curl.get(url).query(options.query || {})
}
}
}
5.3 业务层继承使用
javascript
// 业务项目中的使用方式
const { Controller } = require('@heartvital/heart-elpis')
module.exports = (app) => {
return class UserController extends Controller.Base(app) {
async getUserInfo(ctx) {
try {
const userInfo = await this.service.user.getUserById(ctx.params.id)
this.success(ctx, userInfo)
} catch (error) {
this.fail(ctx, error.message, 500)
}
}
}
}
6、发布流程
6.1 发布前准备
-
注册 npm 账号
bashnpm adduser
-
登录 npm
bashnpm login
-
版本管理
bash# 补丁版本 npm version patch # 次版本 npm version minor # 主版本 npm version major
6.2 发布命令
bash
# 发布公开包
npm publish --access public
# 发布到指定registry
npm publish --registry https://registry.npmjs.org
6.3 发布配置
在package.json
中添加发布配置:
json
{
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"files": ["index.js", "app/", "elpis-core/", "model/", "README.md"]
}
6.4 发布验证
发布后验证包的完整性:
bash
# 安装测试
npm install @heartvital/heart-elpis
# 功能验证
const { serverStart, frontendBuild, Controller } = require('@heartvital/heart-elpis')
// 启动服务
serverStart()
// 构建前端
frontendBuild('production')
总结
Elpis 框架的 npm 包封装采用了以下关键技术:
- 模块化设计:清晰的目录结构和功能分离
- 加载器模式:自动化的文件加载和依赖注入
- 配置合并:支持业务项目的自定义配置
- 路径管理:智能的资源路径解析
- 工程化集成:完整的前端构建流程
这种封装方式不仅提供了开箱即用的能力,还保持了足够的灵活性,让业务项目可以根据需要进行扩展和定制。通过 npm 包的形式发布,极大地提高了框架的可维护性和可复用性。
看到这里才知道,之前做的工作很多都是为了往后的铺垫,总算是拨得云开见月明了。