1.0.0 • Published 1 year ago

@chensi-thunder/fe-micro-app v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

APP前端构建指南

1. (开文强调)全局变量声明注意

尽量不要再在window/vue/vue-router/react中混入属性,避免不同app和主工程之间变量混乱,以及可能难以追踪的内存泄漏

  • windows上主要占用的属性
    1. app主工程依赖 window.exportApp/window.vue/window.vueRouter/window._idss_micro_render
    2. 项目主工程依赖(主要使用element-ui,避免样式覆盖,供app使用) window.ElementUI ...其他暂未发现

  • 现有vue开发工程中,现有项目主工程中,已经在vue库中继承了

    1. axios请求二次封装插件 $requet/$requestCancel
    2. socket相关方法:$socketInit/$socketSend/$socketIsOpen/$socketClose
    3. 刷新当前页面的父级跳转页面方法 $refreshParentPage
    4. 暂无数据抽象service提取变量 $EMPTY_CONFIG
    5. app主工程依赖占用变量:appsList
  • vue-router中,现有项目主工程已经继承的函数或者方法

    1. replaceTagPush 关闭tags并且跳转
    2. pushWithFromPath 新开路由,query中添加from属性,from为父级页面路径
    3. backToFromPath 回到原始路由(与pushWithFromPath搭配使用)

2. 前端使用、构建指南

现有开发使用方式支持vuereact两种前端开发框架开发项目,同时需要依赖fe-micro-app包,对项目进行入口文件以及其他部分代码进行微调,很方便的将一个普通项目改造为一个可以支持独立运行的app。 下面将主要介绍具体使用过程中的一些操作和注意事项

2.1 fe-micro-app 通用依赖包安装

安装app需要依赖文件fe-micro-app,该依赖封装了app内部处理的核心暴露函数、以及webpack的manifest提取plugin

# yarn 方式安装
yarn add fe-micro-app
# npm
npm install fe-micro-app

2.2 vue 搭建和构建

2.2.1 vue搭建框架

建议使用vue-cli搭建 参考链接

# 安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli

2.2.2 创建一个项目

vue-cli搭建使用文档,参考链接

    vue create 英文项目名称xxx

2.2.3 项目构建相关配置

修改vue.config.js文件

// vue.config.js
const appManifestPlugin = require('fe-micro-app/plugin/app-manifest-plugin.js')

const config = {
    ..., // 你需要在vue.confi.js配置的参数
    devServer: {
        ...
        port: 8000, // 建议使用固定端口号,方便开发环境下测试
        ... // 你自己的其他配置
    }
}
module.exports = appManifestPlugin(config, {
  publicPath: '/app-fe/yourAppName', // 部署链接,生产环境的部署地址
  externals: {} // 生产环境打包时,项目自定义的external
})

2.2.4 启动项目

到此为止,一个基础的项目配置已经完成,可以成功运行了。参考链接

    npm run serve
    # 或者
    yarn serve

2.2.5 打包构建

上述vue.config.js文件中,在最后发布上线的时候,publicPath参数一定要配置正确,需要根据自己app部署名称进行匹配,publicPath: '/app-fe/yourAppName',主工程根据该路径来加载资源

npm run build
# 或者
yarn buld

2.3 react 搭建和构建

2.3.1 搭建框架

建议使用creat-react-app搭建react app 参考链接

npx create-react-app my-app
cd my-app
# react项目需要修改webpack配置项,需要启动eject指令,将webpack配置项暴露
npm run eject

2.3.2 项目构建相关配置

  • 修改webpack配置相关处理文件config/webpack.config.js
  const HtmlManifestPlugin = require('fe-micro-app/plugin/html-mainfest-plugin.js')
  const manifest = {}
  module.exports = function () {
    return {
      plugins: [
        /** 将InlineChunkHtmlPlugin 插件注释,该插件将生成的runtime script打入到index.html的文件内部script执行,该方式暂时不支持 */
        // isEnvProduction &&
          //   shouldInlineRuntimeChunk &&
          //   new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
        ...

        // 提取manifest文件
        new HtmlManifestPlugin({ manifest }),

        // plugins 添加manifest插件,并自定义处理manifest
        new ManifestPlugin({
          generate: (seed, files) => {
            // 判断多页面,并处理入口文件
            return Object.assign(seed, manifest)
          }
        })
      ]
    }
  }
  • 修改webpack devServer配置config/webpackDevServer.config.js
  module.exports = function(proxy, allowedHost) {
    return {
  
        // 开环境下作为app调试时,需要设置跨域处理
        headers: {
          'Access-Control-Allow-Origin': '*'
        }
      ...
    }
  }
  • 修改构建后的publicPath处理 config/paths.js

  // publicUrlOrPath变量为webpack内部获取的publicPath变量
    const publicUrlOrPath = getPublicUrlOrPath(
      process.env.NODE_ENV === 'development',
      '你的本地测试path',
      '生产环境path'
    )

2.3.3 启动项目

到此为止,一个基础的项目配置已经完成,可以成功运行了。

    npm start
    # 或者
    yarn start

2.3.4 打包构建

npm run build
# 或者
yarn buld

2.4 vue3 搭建和构建

2.4.1 搭建框架

建议使用@vue/cli@4.x.x搭建vue3项目(目前暂不支持vite构建) 参考链接

vue create my-app
cd my-app

2.4.2 项目构建相关配置

修改vue.config.js文件

// vue.config.js
const appManifestPlugin = require('fe-micro-app/plugin/app-manifest-plugin.js')

const config = {
    ..., // 你需要在vue.confi.js配置的参数
    devServer: {
        ...
        port: 8000, // 建议使用固定端口号,方便开发环境下测试
        ... // 你自己的其他配置
    }
}
module.exports = appManifestPlugin(config, {
  publicPath: '/app-fe/yourAppName', // 部署链接,生产环境的部署地址
  externals: {} // 生产环境打包时,项目自定义的external
})

2.4.3 启动项目

到此为止,一个基础的项目配置已经完成,可以成功运行了。参考链接

    npm run serve
    # 或者
    yarn serve

2.3.4 打包构建

npm run build
# 或者
yarn buld

3. 项目开发

3.1 入口文件修改

vue工程入口

// src/main.js
import { registerApp } from 'fe-micro-app'
    
// 注册app并渲染app
export default registerApp({
  // 用于传递vue作用域,避免提取公共方法后,vue版本不一致问题
  factory: Vue,
  // 用于主函数渲染的对象,不需要包裹new Vue
  app: {
    router,
    el: '#app',
    components: { App: () => import('./App.vue') },
    template: '<App/>',
    // vue的生命周期钩子都接受props属性,用于接收主工程向子工程传递的属性,用于主项目和子app进行通讯
    beforeCreate: (props) => {
      console.log(props)
    }
  },
  // 开发测试阶段api前缀,app声明周期中,用于设置开发环境默认路由
  apiPrefix: '/api'
})

注意点:vue的生命周期函数中,都支持props传递,支持的生命周期函数主要包括vue声明周期钩子

vue3工程入口

// src/main.js
import { createApp } from 'vue'
import { registerApp } from 'fe-micro-app'
import App from './App.vue'
import request from '@/plugins/request.js'
import socket from '@/plugins/socket.js'
import router from '@/router'

export default registerApp({
  factory: createApp,
  // 用于主函数渲染的对象
  app: {
    App,
    el: '#app'
  },
  // 用于在vue3中app初始化成功后,挂载各种插件
  useCallback (app) {
    app.use(request)
    app.use(router)
    app.use(socket)
  },
  // 开发测试阶段api前缀,app声明周期中,用于设置开发环境默认路由
  apiPrefix: '/api'
})

react工程

  1. src/index.js 入口文件修改

      import { registerApp, appConfig } from 'fe-micro-app'
    
      // 路由组件处理,app运行时,会默认为app提供appConfig.routerBase,用于为当前app路由添加nameSpace
      const RouteExample = () => {
        return (
          // appConfig.routerBase为app提供的nameSpace
          <Router basename={appConfig.routerBase}>
            <nav>
              <Link to="/">Home</Link>
              <Divider type="vertical" />
              <Link to="/about">About</Link>
            </nav>
            <Suspense fallback={null}>
              <Switch>
                <Route path="/" exact component={Home} />
                <Route path="/about" component={About} />
              </Switch>
            </Suspense>
          </Router>
        )
      }
      // 入口app组件
      const App = () => {
        return (
          <div className="app-main">
            <LibVersion />
            <HelloModal />
            <Divider />
            <RouteExample />
          </div>
        )
      }
    
      // app暴露方式
      export default registerApp({
        factory: ReactDOM,
        app: {
          template: <App/>,
          el: document.getElementById('root')
        },
        lifeCycles: {
          bootstrap () {
            console.log('[react16] react app bootstraped')
          },
          mount (props) {
            console.log('[react16] props from main framework', props)
          },
          unmount () {
            ReactDOM.unmountComponentAtNode(document.getElementById('root'))
          }
        },
        // 开发测试阶段api前缀,app声明周期中,通过this.api调用
        apiPrefix: '/api'
      })

    注意点:与vue的生命周期函数不同,react在class components中才会有生命周期的钩子函数支持,在函数组件中不能支持。

3.2 业务开发

3.2.1 api前缀部分

在app对接到主工程中,主工程会对当前app的所有api在nginx层进行代理转发,而前端需要对该api进行控制,在主工程,自动对该api前缀进行替换,替换成主工程使用的api前缀,所以主工程会传递一个apiPrefix变量供app开发使用,功能中如果需要使用路由跳转、下钻功能的,见如下示例:

使用方式如下:

import { appConfig } from 'fe-micro-app'
export default {
    methods: {
        async getList () {
            await axios({
              url: `${appConfig.apiPrefix}/ti/userList`,
              method: 'get'
            })
        }
    }
}

3.2.2 websocket 事件前缀部分

在app对接到主工程中,通常会存在全局ws事件进行拦截处理,对不同app的不同事件进行分发,所以主工程会传递一个wsEvPrefix变量供app开发使用,用于区分不同的app websocket前缀,见如下示例:

使用方式如下:

import { appConfig } from 'fe-micro-app'
export default {
    methods: {
      getWsList () {
        this.$socketSend(`${appConfig.wsEvPrefix}query-list`)
      }
    },
    created () {
      // 发送socket请求
      this.getWsList()
      // 监听ws请求
      this.bus.$on(`${appConfig.wsEvPrefix}query-list`, (res) => {
        this.list = res
      })
    }
}

3.2.3 host当前资源请求

在app工程中,存在部分场景即获取当前工程下的静态资源的情况,为了支持该种情况,添加了host支持,用于满足工程中对于该需求的使用场景

使用方式如下:

import { appConfig } from 'fe-micro-app'
export default {
  methods: {
    async getList () {
      const data = (await (await fetch(`${appConfig.host}/config.json`', {
        method: 'get',
        headers: new Headers({
          'Content-Type': 'application/json'
        })
      })).json()).content
      return data
    }
  }
}

3.2.4 区分当前app是独立渲染还是app渲染

在项目开发中,通常会遇到一些需要判断当前项目为app渲染还是普通渲染的场景,针对不同的场景有不同的代码处理逻辑,如下示例:

下面的示例展示的是项目二级菜单在app渲染时append to body, 否则在父级节点下展示

  <template>
    <app-menu
      :isMenuOpen="isMenuOpen"
      :popperAppendToBody="menuPopperAppendToBody"></app-menu>
  </template>

  <script>
    // 判断当前工程为app渲染还是普通渲染
    import { checkIsMicroRender } from 'fe-micro-app'
    export default {
      data () {
        return {
          // menu 菜单的二级菜单popper弹框是否append to body判断,app渲染时false,否则为true
          menuPopperAppendToBody: !checkIsMicroRender()
        }
      }
    }
  </script>

3.2.5 app内部跳转外部链接

import { appHistory } from 'fe-micro-app'

export default {
  methods: {
    gotoMainAppHome () {
      // 跳转到主框架的用户管理页面
      appHistory.push('/manage/manage-base/user')

      // 当前路由页面替换为主框架的用户管理页面
      appHistory.replace('/manage/manage-base/user')
    }
  }
}

4. 开发中注意事项

  1. 现有支持功能有限,建议vue/vue-template-compiler保持版本使用2.6.10
  2. 全局变量升名注意事项,见开头
  3. 样式尽量使用scoped方式升名,避免样式冲突,优先推荐BEM、CSS Modules 方案管理样式
  4. 尽量使用import('xxxx/xxx.vue')动态引入的方式,降低主入口文件的体积
  5. 尽量不要使用全局样式,以及添加reset css,这样可能造成整个主框架应用显示异常(后期优化,尽量做到css隔离)
  6. 目前建议不要在在vue.prototype上挂载任何实例,避免主工程和其他app发生方法、属性覆盖的情况
  7. 如果需要在测试开发环境中的app时,需要为webpack等构建工具添加publicPath(注:vue-cli方式生成的app工程不需要修改,已经设置内置publicPath)
  8. 关于ui框架,目前主工程使用了element-ui,所以如果想使用相同ui框架,建议处理方式

    • 主入口文件修改
    // main.js
    Vue.use(window.ElementUI || ElementUI)
    if (!window.ElementUI) {
      // eslint-disable-next-line
      import('element-ui/lib/theme-chalk/index.css')
    }
    • vue.confi.js修改
    module.exports = appManifestPlugin(config, {
      publicPath: '/app-fe/yourAppName', // 部署链接,生产环境的部署地址
      // 生产环境打包时,项目自定义的external
      externals: {
        'element-ui': 'window.ElementUI'
      }
    })
  9. 关于public静态资源文件夹下的资源,无法添加publicPath,主要原因是任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack,所以需要使用绝对路径来引用,所以针对静态资源使用方式

    在模板中,你首先需要向你的组件传入基础 URL:

      <template><img :src="`${publicPath}my-image.png`"></template>
      <script>
        export default {
          data () {
            return {
              publicPath: process.env.BASE_URL
            }
          }
        }
      </script>

    在css中,根据项目使用的不同编译器,可以使用变量,postcss可以通过postcss-function插入函数引入

  yarn add postcss-functions@3.0.0 -D
  // postcss.config.js
  module.exports = {
    ...
      'postcss-functions': {
        functions: {
          staticUrl (url, type = '{path}') {
            return type.replace(`{path}`, `${process.env.BASE_URL}${url}`)
          }
        }
      }
    ...
  }
  <style scoped lang="postcss">
  #home {
    ...
    background: staticUrl(/403.png, url({path}));
    ...
  }
  </style>