npm 包抽象封装并发布完整指南

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"
}

关键配置说明:

  1. name 字段 :使用@npm帐号/elpis的形式,方便搜索识别,支持 npm 组织范围包管理
  2. main 字段 :指定入口文件为index.js,这是框架对外暴露 API 的核心文件
  3. 版本管理:采用语义化版本控制,确保版本更新的规范性

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 发布前准备

  1. 注册 npm 账号

    bash 复制代码
    npm adduser
  2. 登录 npm

    bash 复制代码
    npm login
  3. 版本管理

    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 包封装采用了以下关键技术:

  1. 模块化设计:清晰的目录结构和功能分离
  2. 加载器模式:自动化的文件加载和依赖注入
  3. 配置合并:支持业务项目的自定义配置
  4. 路径管理:智能的资源路径解析
  5. 工程化集成:完整的前端构建流程

这种封装方式不仅提供了开箱即用的能力,还保持了足够的灵活性,让业务项目可以根据需要进行扩展和定制。通过 npm 包的形式发布,极大地提高了框架的可维护性和可复用性。

看到这里才知道,之前做的工作很多都是为了往后的铺垫,总算是拨得云开见月明了。

相关推荐
小样还想跑14 分钟前
axios无感刷新token
前端·javascript·vue.js
Java水解23 分钟前
一文了解Blob文件格式,前端必备技能之一
前端
用户38022585982444 分钟前
vue3源码解析:响应式机制
前端·vue.js
bo521001 小时前
浏览器渲染机制详解(包含渲染流程、树结构、异步js)
前端·面试·浏览器
普通程序员1 小时前
Gemini CLI 新手安装与使用指南
前端·人工智能·后端
山有木兮木有枝_1 小时前
react受控模式和非受控模式(日历的实现)
前端·javascript·react.js
流口水的兔子1 小时前
作为一个新手,如果让你去用【微信小程序通过BLE实现与设备通讯】,你会怎么做,
前端·物联网·微信小程序
多啦C梦a1 小时前
🪄 用 React 玩转「图片识词 + 语音 TTS」:月影大佬的 AI 英语私教是怎么炼成的?
前端·react.js
呆呆的心1 小时前
大厂面试官都在问的 WEUI Uploader,源码里藏了多少干货?🤔
前端·微信·面试
heartmoonq1 小时前
深入理解 Vue 3 响应式系统原理:Proxy、Track 与 Trigger 的协奏曲
前端