Skip to content

Commit

Permalink
fix: 修复了元素查询时带有特殊字符导致报错的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
bailicangdu committed Nov 5, 2021
1 parent aee6c8c commit 8c4b081
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 138 deletions.
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</header>
<section class='introduce'>
<div class='introduce-title'>MicroApp</div>
<p class='introduce-desc'>一种用于构建微前端应用的极简方案</p>
<p class='introduce-desc'>一款轻量、高效、功能强大的微前端框架</p>
<div class="introduce-btn-list">
<a href="./docs.html" class='btn-start'>开始使用</a>
<a href="https://zeroing.jd.com/micro-app/demo/" class='btn-coding' target="blank">在线案例</a>
Expand Down
73 changes: 56 additions & 17 deletions docs/zh-cn/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ microApp.start({
> 1、如果跨域请求带cookie,那么`Access-Control-Allow-Origin`不能设置为`*`,这一点需要注意
## 2、适配vite
当子应用是vite应用时需要做特别的适配,适配vite的代价是巨大的,我们必须关闭沙箱功能,因为沙箱在`module script`下不支持,这导致大部分功能失效,包括:环境变量、样式隔离、元素隔离、数据通信、资源地址补全、baseroute 等。
当子应用是vite应用时需要做特别的适配,适配vite的代价是巨大的,我们必须关闭沙箱功能,因为沙箱在`module script`下不支持,这导致大部分功能失效,包括:环境变量、样式隔离、元素隔离、资源地址补全、baseroute 等。

在嵌入vite子应用时,`micro-app`的功能只负责渲染,其它的行为由应用自行决定,这包括如何防止样式、JS变量、元素的冲突。

Expand All @@ -47,6 +47,9 @@ microApp.start({

##### 1、修改vite.config.js
```js
import { join } from 'path'
import { writeFileSync } from 'fs'

// vite.config.js
export default defineConfig({
base: `${process.env.NODE_ENV === 'production' ? 'http://my-site.com' : ''}/basename/`,
Expand All @@ -57,32 +60,32 @@ export default defineConfig({
let basePath = ''
return {
name: "vite:micro-app",
apply: 'build', // 只在生产环境生效
apply: 'build',
configResolved(config) {
// 获取资源地址前缀
basePath = `${config.base}${config.build.assetsDir}/`
},
renderChunk(code, chunk) {
// build后,import会通过相对地址引入模块,需要将其补全
if (chunk.fileName.endsWith('.js') && /(from|import)(\s*['"])(\.\.?\/)/g.test(code)) {
code = code.replace(/(from|import)(\s*['"])(\.\.?\/)/g, (all, $1, $2, $3) => {
return all.replace($3, new URL($3, basePath))
})
writeBundle (options, bundle) {
for (const chunkName in bundle) {
if (Object.prototype.hasOwnProperty.call(bundle, chunkName)) {
const chunk = bundle[chunkName]
if (chunk.fileName && chunk.fileName.endsWith('.js')) {
chunk.code = chunk.code.replace(/(from|import\()(\s*['"])(\.\.?\/)/g, (all, $1, $2, $3) => {
return all.replace($3, new URL($3, basePath))
})
const fullPath = join(options.dir, chunk.fileName)
writeFileSync(fullPath, chunk.code)
}
}
}
return code
}
},
}
})(),
],
})
```

##### 2、路由
vite环境下,当路由的baseName和vite.base值不相等,两者会进行拼接,这导致无法自定义baseName来适配基座应用的路由。

有两种方式解决这个问题:
- 方式一:子应用使用hash路由
- 方式二:子应用根据基座路由单独打包一个版本,这个版本的子应用无法单独访问,必须嵌入基座中运行。
推荐子应用使用hash路由,避免一些可能出现的问题。

##### 3、静态资源
图片等静态资源需要使用绝对地址,可以使用 `new URL('../assets/logo.png', import.meta.url).href` 等方式获取资源的全链接地址。
Expand Down Expand Up @@ -127,7 +130,43 @@ microApp.start({
})
```

> [!TIP]
### vite数据通信
沙箱关闭后,子应用默认的通信功能失效,此时可以通过手动注册通信对象实现一致的功能。

**注册方式:在基座应用中为子应用初始化通信对象**

```js
import { EventCenterForMicroApp } from '@micro-zoe/micro-app'

// 注意:每个vite子应用根据appName单独分配一个通信对象
window.eventCenterForViteApp1 = new EventCenterForMicroApp(appName)
```

vite子应用就可以通过注册的`eventCenterForViteApp1`对象进行通信,其api和`window.microApp`一致,*而基座通信方式没有任何变化。*

**子应用通信方式:**
```js
/**
* 绑定监听函数
* dataListener: 绑定函数
* autoTrigger: 在初次绑定监听函数时有缓存数据,是否需要主动触发一次,默认为false
*/
window.eventCenterForViteApp1.addDataListener(dataListener: (data: Object) => void, autoTrigger?: boolean)

// 解绑指定函数
window.eventCenterForViteApp1.removeDataListener(dataListener)

// 清空当前子应用的所有绑定函数(全局数据函数除外)
window.eventCenterForViteApp1.clearDataListener()

// 主动获取数据
window.eventCenterForViteApp1.getData()

// 子应用向基座应用发送数据
window.eventCenterForViteApp1.dispatch({type: '子应用发送的数据'})
```

> [!WARNING]
> 1、关闭沙箱后的子应用可以直接访问全局window,可以通过挂载全局变量来进行数据通信和其它操作。
>
> 2、适配vite本质上是适配module脚本,其它非vite构建的module脚本也可以采用相同的思路处理。
Expand Down
7 changes: 6 additions & 1 deletion docs/zh-cn/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@

`2021-11-05`

- **New**

- 🆕 新增了`EventCenterForMicroApp`方法,用于沙箱关闭时实现通信功能(如vite)

- **Bug Fix**

- 🐞 修复了在不支持`ShadowRoot`的浏览器中的报错问题
- 🐞 修复了在不支持`ShadowRoot`的浏览器中的报错问题,fix [#134](https://github.com/micro-zoe/micro-app/issues/134)
- 🐞 修复了元素查询时带有特殊字符导致报错的问题,fix [#140](https://github.com/micro-zoe/micro-app/issues/140)


### 0.4.2
Expand Down
79 changes: 40 additions & 39 deletions docs/zh-cn/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,46 @@

如果想要同时向多个子应用发送数据,可以看一下节[全局数据通信](/zh-cn/data?id=全局数据通信)

### 1、基座应用向子应用发送数据

### 1、子应用获取来自基座应用的数据
`micro-app`会向子应用注入名称为`microApp`的全局对象,子应用通过这个对象和基座应用进行数据交互。

有两种方式获取来自基座应用的数据:

**方式1:绑定/解绑监听函数**

监听函数只有在数据变化时才会触发。
```js
function dataListener (data) {
console.log('来自基座应用的数据', data)
}

/**
* 绑定监听函数
* dataListener: 绑定函数
* autoTrigger: 在初次绑定监听函数时有缓存数据,是否需要主动触发一次,默认为false
* 补充: autoTrigger主要是为子应用提供的,因为子应用是异步渲染的,如果在子应用还没渲染时基座应用发送数据,子应用在初始化后不会触发绑定函数,但这个数据会放入缓存中,此时可以设置autoTrigger为true主动触发一次监听函数来获取数据。
*/
window.microApp?.addDataListener(dataListener: Function, autoTrigger?: boolean)

// 解绑指定函数
window.microApp?.removeDataListener(dataListener)

// 清空当前子应用的所有绑定函数(全局数据函数除外)
window.microApp?.clearDataListener()
```
**方式2:主动获取数据**
```js
window.microApp?.getData() // 返回data数据
```
### 2、子应用向基座应用发送数据
```js
window.microApp?.dispatch({type: '子应用发送的数据'})
```
### 3、基座应用向子应用发送数据
基座应用向子应用发送数据有两种方式:
**方式1: 通过data属性发送数据**
Expand Down Expand Up @@ -56,44 +95,6 @@
microApp.setData('my-app', {type: '新的数据'})
```
### 2、子应用获取来自基座应用的数据
`micro-app`会向子应用注入名称为`microApp`的全局对象,子应用通过这个对象和基座应用进行数据交互。

有两种方式获取来自基座应用的数据:

**方式1:绑定/解绑监听函数**

监听函数只有在数据变化时才会触发。
```js
function dataListener (data) {
console.log('来自基座应用的数据', data)
}

/**
* 绑定监听函数
* dataListener: 绑定函数
* autoTrigger: 在初次绑定监听函数时有缓存数据,是否需要主动触发一次,默认为false
* 补充: autoTrigger主要是为子应用提供的,因为子应用是异步渲染的,如果在子应用还没渲染时基座应用发送数据,子应用在初始化后不会触发绑定函数,但这个数据会放入缓存中,此时可以设置autoTrigger为true主动触发一次监听函数来获取数据。
*/
window.microApp?.addDataListener(dataListener: Function, autoTrigger?: boolean)

// 解绑指定函数
window.microApp?.removeDataListener(dataListener)

// 清空当前子应用的所有绑定函数(全局数据函数除外)
window.microApp?.clearDataListener()
```
**方式2:主动获取数据**
```js
window.microApp?.getData() // 返回data数据
```
### 3、子应用向基座应用发送数据
```js
window.microApp?.dispatch({type: '子应用发送的数据'})
```
### 4、基座应用获取来自子应用的数据
基座应用获取来自子应用的数据有三种方式:
Expand Down
17 changes: 16 additions & 1 deletion examples/children/vite/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const routes = [
// app.use(router)
// app.mount('#vite-app')


let app = null
let router = null
let history = null
Expand All @@ -35,12 +34,28 @@ function mount () {
app.mount('#vite-app')

console.log('微应用child-vite渲染了')

// eventCenterForVite 是基座添加到window的数据通信对象
// 主动获取基座下发的数据
console.log('child-vite getData:', window.eventCenterForVite?.getData())

// 监听基座下发的数据变化
window.eventCenterForVite?.addDataListener((data) => {
console.log('child-vite addDataListener:', data)
})

// 向基座发送数据
setTimeout(() => {
window.eventCenterForVite?.dispatch({ myname: 'child-vite' })
}, 3000)
}

// 将卸载操作放入 unmount 函数
function unmount () {
app?.unmount()
history?.destroy()
// 卸载所有数据监听函数
window.eventCenterForVite?.clearDataListener()
app = null
router = null
history = null
Expand Down
13 changes: 11 additions & 2 deletions examples/main-react16/src/pages/vite/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event'
import { useState } from 'react'
import { Button, Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { EventCenterForMicroApp } from '@micro-zoe/micro-app'
import config from '../../config'
import './vite.less'

// 注册子应用vite的数据通信对象
window.eventCenterForVite = new EventCenterForMicroApp('vite')

const antIcon = <LoadingOutlined style={{ fontSize: 30 }} spin />

function vite () {
Expand All @@ -18,17 +22,21 @@ function vite () {
console.log('生命周期: vite 渲染完成了')
}

function handleDataChange (e) {
console.log('来自 vite 子应用的数据', e.detail.data)
}

return (
<div>
{/* <div className='btn-con'>
<div className='btn-con'>
<Button
type='primary'
onClick={() => changeData({from: '来自基座的数据' + (+new Date())})}
style={{width: '120px'}}
>
发送数据
</Button>
</div> */}
</div>
{
showLoading && <Spin indicator={antIcon} />
}
Expand All @@ -39,6 +47,7 @@ function vite () {
data={data}
// onBeforemount={() => hideLoading(false)}
onMounted={handleMounted}
onDataChange={handleDataChange}
// destroy
// inline
disableSandbox
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = {
],
globals: {
__DEV__: true,
__TEST__: true,
'ts-jest': {
tsconfig: {
target: 'es5',
Expand Down
7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@micro-zoe/micro-app",
"version": "0.4.2",
"description": "A minimalist solution for building micro front-end applications",
"version": "0.4.3",
"description": "A lightweight, efficient and powerful micro front-end framework",
"private": false,
"main": "lib/index.min.js",
"module": "lib/index.esm.js",
Expand Down Expand Up @@ -82,9 +82,6 @@
]
},
"dependencies": {},
"peerDependencies": {
"@babel/runtime": ">=7.0.0"
},
"devDependencies": {
"@babel/core": "~7.12.10",
"@babel/plugin-transform-runtime": "~7.12.10",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const commonPlugins = [
replace({
preventAssignment: true,
__VERSION__: version,
__TEST__: 'false',
})
]

Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/libs/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,11 @@ test('get element that not marked by micro-app', () => {
test('coverage of isNull function', () => {
expect(Utils.isNull(null)).toBeTruthy()
})

test('coverage of isInvalidQuerySelectorKey', () => {
// @ts-ignore
window.__TEST__ = false
expect(Utils.isInvalidQuerySelectorKey('a&bc')).toBeTruthy()
// @ts-ignore
window.__TEST__ = true
})
Loading

0 comments on commit 8c4b081

Please sign in to comment.