@chensi-thunder/fe-micro-app
v1.0.0
Published
特定微前端app入口框架
Downloads
1
Readme
APP前端构建指南
1. (开文强调)全局变量声明注意
尽量不要再在window/vue/vue-router/react中混入属性,避免不同app和主工程之间变量混乱,以及可能难以追踪的内存泄漏
windows上主要占用的属性
- app主工程依赖
window.exportApp/window.vue/window.vueRouter/window._idss_micro_render
- 项目主工程依赖(主要使用element-ui,避免样式覆盖,供app使用)
window.ElementUI
...其他暂未发现
- app主工程依赖
现有vue开发工程中,现有项目主工程中,已经在vue库中继承了
- axios请求二次封装插件
$requet/$requestCancel
- socket相关方法:
$socketInit/$socketSend/$socketIsOpen/$socketClose
- 刷新当前页面的父级跳转页面方法
$refreshParentPage
- 暂无数据抽象service提取变量
$EMPTY_CONFIG
- app主工程依赖占用变量:
appsList
- axios请求二次封装插件
vue-router中,现有项目主工程已经继承的函数或者方法
replaceTagPush
关闭tags并且跳转pushWithFromPath
新开路由,query中添加from属性,from为父级页面路径backToFromPath
回到原始路由(与pushWithFromPath搭配使用)
2. 前端使用、构建指南
现有开发使用方式支持vue
和react
两种前端开发框架开发项目,同时需要依赖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/[email protected]
搭建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工程
- 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. 开发中注意事项
现有支持功能有限,建议vue/vue-template-compiler保持版本使用2.6.10
全局变量升名注意事项,见开头
样式尽量使用
scoped
方式升名,避免样式冲突,优先推荐BEM、CSS Modules 方案管理样式尽量使用
import('xxxx/xxx.vue')
动态引入的方式,降低主入口文件的体积尽量不要使用
全局样式
,以及添加reset css
,这样可能造成整个主框架应用显示异常(后期优化,尽量做到css隔离)目前建议不要在在vue.prototype上挂载任何实例,避免主工程和其他app发生方法、属性覆盖的情况
如果需要在测试开发环境中的app时,需要为
webpack
等构建工具添加publicPath
(注:vue-cli方式生成的app工程不需要修改,已经设置内置publicPath)关于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' } })
关于
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 [email protected] -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>