elpis NPM包的抽离

前几篇文章主要是将如何完成一个elpis项目。本文主要是根据抽离点将之前所讲的elpis项目抽离成sdk供其他项目所使用。

  1. 将启动项目的能力暴露给调用方

    scss 复制代码
        /**
       * 服务端基础
       */
      Controller: {
        Base: require('./app/controller/base.js')
      },
      Service: {
        Base: require('./app/service/base.js')
      },
      /**
       * 编译构建前端工程
       * @params env 环境变量 local/production
       */
      frontendBuild(env) {
        if (env === 'local') {
          FEBuildDev();
        } else if (env === 'production') {
          FEBuildProd();
        }
      },
      /**
       * 启动elpis
       * @params options 项目配置,透传到elpis-core
       */
      serverStart(options = {}){
        const app = ElpisCore.start(options)
        return app;
      }
  2. 修改工程化的配置

    在启动的项目的时候,保留npm包功能的同时可以根据项目配置扩展sdk的工程化能力

    1. npm包内容的修改
    ini 复制代码
    // 获取 elpis/app/目录下所有入口文件
    const entryList = path.resolve(__dirname,'../../pages/**/entry.*.js');
    1. 项目内容的扩展
    less 复制代码
    // 获取 business/app/目录下所有入口文件
    const businessList = path.resolve(process.cwd(),'./app/pages/**/entry.*.js');
    ​
    // 构造 相关webpack 处理的数据结构
    function handleFile(file, entries = {}, htmlWbpackPluginList = []){
      const entryName = path.basename(file,'.js')
       // path.basename(path.dirname(filePath)); // 提取页面名称
      // 构造entry
      entries[entryName] = file;
      // 构造html页面
      htmlWbpackPluginList.push(new HtmlWebpackPlugin({ // 辅助注入打包后的bundle文件到tpl文件中
        //产物 最终模板输出路径
        filename: path.resolve(process.cwd(),'./app/public/dist/',`${entryName}.tpl`),
        //指定要使用的模板文件
        template: path.resolve(__dirname,'../../view/entry.tpl'),
        // 要注入的代码块
        chunks: [entryName]
    ​
      }))
    }
    ​
    1. 扩展能力
    javascript 复制代码
    ​
    // 加载业务 webpack配置
    let businessWebpackConfig = {}
    try {
      businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`)
    } catch (e) {
    ​
    }
    1. npm包对npm引入第三方内容的修改
    css 复制代码
    module: {
        rules:[
          {
            test: /.vue$/,
            use: {
              loader: require.resolve('vue-loader')
            }
          },{
            test: /.js$/,
            include: [
              // 只对业务代码进行 babel 加快webpack的打包速度
              path.resolve(process.cwd(),'./app/pages'),
              // 处理elpis 目录
              path.resolve(__dirname,'../../pages')
            ],
            use:{
              loader: require.resolve('babel-loader')
            }
          },
          {
            test: /.(png|jpe?g|gif)(?.+)?$/,
            use: {
              loader: require.resolve('url-loader'),
              options: {
                limit: 300,
                esModule: false
              }
            }
          },{
            test: /.css$/,
            use: [require.resolve('style-loader'),require.resolve('css-loader')]
          },{
            test: /.less/,
            use: [require.resolve('style-loader'),require.resolve('css-loader'),require.resolve('less-loader')]
          },{
            test: /.(eot|svg|ttf|woff|woff2)(?\S*)?$/,
            use: require.resolve('file-loader')
          }
        ]
  3. Elpis-core的配置

    核心思想是在获取elpis npm包的同时,可以获取到项目的配置文件。将两组配置混合

    • 全局中间件的配置修改

      javascript 复制代码
      // 注册npm包的全局中间件
      const elipsMiddlewarePath = path.resolve(__dirname,`..${sep}app${sep}middleware.js`)
      const elpisMiddleware = require(elipsMiddlewarePath)
      elpisMiddleware(app)
      ​
      // 注册项目的全局中间件
      try{
        require(`${app.businessPath}${sep}middleware.js`)(app)
      }catch(e){
      }
    • Middleware,Router-schema,Controller,Service,extend 的修改大致相同

      ini 复制代码
      // 读取 /elpis/app/middleware/**/**.js下所有的文件
        const elpisMiddlewarePath = path.resolve(__dirname,`..${sep}..${sep}app${sep}xxx`);
        const elpisFileList = glob.sync(path.resolve(elpisMiddlewarePath,`.${sep}**${sep}**.js`))
        elpisFileList.forEach(file => {
          handleFile(file)
        });
      ​
        // 读取 业务根目录/app/middleware/**/**.js下所有的文件
        const businessMiddlewarePath = path.resolve(app.businessPath,`.${sep}xxx`);
        const businessFileList = glob.sync(path.resolve(businessMiddlewarePath,`.${sep}**${sep}**.js`))
        businessFileList.forEach(file => {
          handleFile(file)
        });
        // 把内容加载到 app.middlewares下
    • config配置

      javascript 复制代码
      // elpis config 目录及相关文件
        const elpisConfigPath = path.resolve(__dirname,`..${sep}..${sep}config`)
        let defaultConfig = require(path.resolve(elpisConfigPath,`.${sep}config.default.js`))
      ​
        // 获取业务 config 目录及相关文件
        // 知道config目录
        const businessConfigPath = path.resolve(process.cwd(),`.${sep}config`)
        
        try{
          defaultConfig = {
            ...defaultConfig,
            ...require(path.resolve(businessConfigPath,`.${sep}config.default.js`))
          }
          
        }catch(error) {
          if(error.code === 'ENOENT'){
            onsole.log('[exception] there is no default.config file');
          }else if(error.message?.includes('Syntax error')){
            console.log('[exception] default.config Syntax error');
          }else{
            console.log('[exception] efault.config error');
          }
        }
    • Router

      ini 复制代码
      // 找到elpis路由文件路径
        const elpisRouterPath = path.resolve(__dirname,`..${sep}..${sep}app${sep}router`);
        
        // 注册elpis所有路由
        const elpisFileList = glob.sync(path.resolve(elpisRouterPath,`.${sep}**${sep}**.js`));
        elpisFileList.forEach(file => {
          handleFile(file)
        })
      ​
        // 找到业务路由文件路径
        const businessRouterPath = path.resolve(app.businessPath,`.${sep}router`);
        
        // 注册业务所有路由
        const businessFileList = glob.sync(path.resolve(businessRouterPath,`.${sep}**${sep}**.js`));
        businessFileList.forEach(file => {
          handleFile(file)
        })
  4. 项目组件和页面的集成

    修改webpack的alias 将可能用到的文件配置先定义好,在npm包集成的时候将 项目和npm包的内容集成一起初始化。

    scss 复制代码
      resolve: {
        extensions: [ '.vue', '.js', '.less', '.css'],
        alias: (() => {
          const aliasMap = {};
          const blackModulePath = path.resolve(__dirname, '../libs/blank.js')
    ​
          // dashboard 路由扩展配置
    ​
          const businessDashboardRouterConfig = path.resolve(process.cwd(), './app/pages/dashboard/router.js');
          aliasMap['$businessDashboardRouterConfig'] = fs.existsSync(businessDashboardRouterConfig) ? businessDashboardRouterConfig : blackModulePath
    ​
          // schema-view component扩展配置
          const businessComponentConfig = path.resolve(process.cwd(), './app/pages/dashboard/complex-view/schema-view/components/component-config.js');
          aliasMap['$businessComponentConfig'] = fs.existsSync(businessComponentConfig) ? businessComponentConfig : blackModulePath
    ​
          // schema-form item 扩展配置
          const businessFormItemConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-form/form-item-config.js');
          aliasMap['$businessFormItemConfig'] = fs.existsSync(businessFormItemConfig) ? businessFormItemConfig : blackModulePath
    ​
    ​
                // schema-search-bar item 扩展配置
          const businessSearchItemConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-search-bar/search-item-config.js');
          aliasMap['$businessSearchItemConfig'] = fs.existsSync(businessSearchItemConfig) ? businessSearchItemConfig : blackModulePath
    ​
    ​
    ​
    ​
          return {
            'vue': require.resolve('vue'),
            '@babel/runtime/helpers/asyncToGenerator': require.resolve('@babel/runtime/helpers/asyncToGenerator'),
            '@babel/runtime/regenerator': require.resolve('@babel/runtime/regenerator'),
            '$elpisPages': path.resolve(__dirname, '../../pages'),
            '$elpisCommon': path.resolve(__dirname, '../../pages/common'),
            '$elpisCurl': path.resolve(__dirname, '../../pages/common/curl.js'),
            '$elpisUtil': path.resolve(__dirname, '../../pages/common/utils.js'),
            '$elpisWidgets': path.resolve(__dirname, '../../pages/widgets'),
            '$elpisHeaderContainer': path.resolve(__dirname, '../../pages/widgets/header-container/header-container.vue'),
            '$elpisSiderContainer': path.resolve(__dirname, '../../pages/widgets/sider-container/sider-container.vue'),
            '$elpisSchemaTable': path.resolve(__dirname, '../../pages/widgets/schema-table/schema-table.vue'),
            '$elpisSchemaForm': path.resolve(__dirname, '../../pages/widgets/schema-form/schema-form.vue'),
            '$elpisSechemaSearchBar': path.resolve(__dirname, '../../pages/widgets/schema-search-bar/schema-search-bar.vue'),
            '$elpisStore': path.resolve(__dirname, '../../pages/store'),
            '$elpisBoot': path.resolve(__dirname, '../../pages/boot.js'),
            ...aliasMap
          }
        })()
      },
    ​
    • 业务 拓展路由
    javascript 复制代码
    import businessDashboardRouterConfig from '$businessDashboardRouterConfig'
    if (typeof businessDashboardRouterConfig === 'function') {
      businessDashboardRouterConfig({routes, siderRoutes})
    }
    • 业务组件的扩展
    javascript 复制代码
    // 业务扩展components配置
    import BusinessComponentConfig  from '$businessComponentConfig'
    export default {
      ...ComponentConfig,
      ...BusinessComponentConfig
    }
    ​
    //业务扩展 search-item配置
    import BusinessSearchItemConfig from '$businessSearchItemConfig'
    export default {
      ...SearchItemConfig,
      ...BusinessSearchItemConfig
    }
    • 基础表单组件的扩展
    javascript 复制代码
    //业务扩展 form-item 配置
    import BusinessFormItemConfig from '$businessFormItemConfig'
    export default {
      ...FormItemConfig,
      ...BusinessFormItemConfig
    };
  5. npm的发布流程

    1. 登录 npm 终端执行登录命令,输入账号密码:

      复制代码
      npm login

      注意:如果之前使用过淘宝镜像(如 registry=https://registry.npm.taobao.org),需先切换回官方源: npm config set registry https://registry.npmjs.org/

    2. 发布包 在项目根目录执行:

      复制代码
      npm publish
      arduino 复制代码
      npm publish --access public  // 带有前缀的 命名空间第一次发布

      成功后会显示发布信息,此时可在 npm 官网 搜索你的包名查看。

    3. 更新包版本

      1. 更新版本号(遵循语义化版本):

        bash 复制代码
        npm version patch  # 修订号 +1(1.0.0 → 1.0.1,修复 bug 时用)
        npm version minor  # 次版本号 +1(1.0.1 → 1.1.0,新增功能时用)
        npm version major  # 主版本号 +1(1.1.0 → 2.0.0,不兼容变更时用)

        该命令会自动修改 package.jsonversion 字段,并创建 git 标签(如果使用 git)。

      2. 重新发布

        bash 复制代码
        npm publish  # 发布新版本

    五、删除 / 废弃包(谨慎操作)

    • 删除指定版本(发布后 72 小时内可删):

      sql 复制代码
      npm unpublish 包名@版本号  # 例如:npm unpublish my-unique-package@1.0.0
    • 废弃包(不删除但标记为不推荐使用):

      arduino 复制代码
      npm deprecate 包名 "该包已废弃,请使用 xxx 替代"

经过这次项目的完成,让我对前端这块的内容有了一个梳理.对于后续提升自己的能力有了一个大致的方向。

备注引用: 抖音"哲玄前端"《大前端全栈实践》

相关推荐
成小白7 小时前
前端实现连词搜索下拉效果
前端·javascript
卸任7 小时前
从0到1搭建react-native自动更新(OTA和APK下载)
前端·react native·react.js
OpenTiny社区7 小时前
OpenTiny NEXT 训练营实操体验 | 四步将你的 Web 应用升级为智能应用
前端·开源·ai编程
持续迷茫8 小时前
lint-staged 中 --verbose 选项的深度解析
前端·git
拜无忧8 小时前
带有“水波纹”或“扭曲”效果的动态边框,进度条
前端
12码力8 小时前
open3d 处理rgb-d深度图
前端
小猪猪屁8 小时前
WebAssembly 从零到实战:前端性能革命完全指南
前端·vue.js·webassembly
EMT8 小时前
记一个Vue.extend的用法
前端·vue.js
RaidenLiu8 小时前
Riverpod 3:组合与参数化的进阶实践
前端·flutter