forked from opentiny/tiny-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(block): add block canvas render
- Loading branch information
Showing
8 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# @opentiny/tiny-engine-block-render | ||
|
||
将区块schema进行编译渲染,以便能够在画布中实时显示 | ||
|
||
## 使用 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "@opentiny/tiny-engine-block-render", | ||
"version": "1.0.2", | ||
"description": "Compile the block to render in the canvas", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"build": "vite build" | ||
}, | ||
"keywords": [ | ||
"vue", | ||
"vue3", | ||
"frontend", | ||
"opentiny", | ||
"lowcode", | ||
"tiny-engine", | ||
"webComponent" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/opentiny/tiny-engine", | ||
"directory": "packages/compilerBlockToRender" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/opentiny/tiny-engine/issues" | ||
}, | ||
"author": "OpenTiny Team", | ||
"license": "MIT", | ||
"homepage": "https://opentiny.design/tiny-engine", | ||
"dependencies": { | ||
"vue": "^3.4.15", | ||
"@vue/compiler-sfc": "^3.4.34", | ||
"@vue/compiler-dom": "^3.4.34", | ||
"vue3-sfc-loader": "^0.9.5" | ||
}, | ||
"devDependencies": { | ||
"@vitejs/plugin-vue": "^4.5.2", | ||
"@vitejs/plugin-vue-jsx": "^3.1.0", | ||
"vite": "^4.3.7" | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/compilerBlockToRender/src/components/AsyncComponent.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<template> | ||
<div> | ||
<Suspense> | ||
<component :is="BlockComponent"></component> | ||
<template #fallback>正在加载...</template> | ||
</Suspense> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import { defineProps, defineAsyncComponent } from 'vue' | ||
import { generateBlock } from '../utils' | ||
const props = defineProps({ | ||
blockConfig: { | ||
type: Object, | ||
default: () => ({ | ||
code: `<template> | ||
<h4>请传递对应区块源码</h4> | ||
</template>` | ||
}) | ||
} | ||
}) | ||
const BlockComponent = defineAsyncComponent(async () => { | ||
return await generateBlock(props.blockConfig) | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { default as AsyncComponent } from './src/components/AsyncComponent' | ||
export * from './src/utils' |
127 changes: 127 additions & 0 deletions
127
packages/compilerBlockToRender/src/utils/compiler-sfc.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import * as compiler from '@vue/compiler-sfc' | ||
import { getBlobURL, generateID, joinMap } from './utils' | ||
|
||
export const blocksMap = {} | ||
const styleSheetMap = {} | ||
window.blocksMap = blocksMap | ||
|
||
/** | ||
* adoptedStyleSheets 的优先级高于document.styleSheets,它是一个Proxy<Array> 对象 | ||
* adoptedStyleSheets 不仅支持document,还会往shadowRoot节点下挂载样式,同一个stylesheet可以挂到多处,可复用,节约内存 | ||
* adoptedStyleSheets 下的样式表对象,在head下不可见,不会影响dom结构 | ||
*/ | ||
|
||
const generateCssInJs = (descriptor, id) => { | ||
if (descriptor.styles) { | ||
const styled = descriptor.styles.map((style) => { | ||
return compiler.compileStyle({ | ||
id, | ||
source: style.content, | ||
scoped: style.scoped, | ||
preprocessLang: style.lang | ||
}) | ||
}) | ||
const styles = styled.map((s) => s.code).join('') | ||
const styleSheet = new CSSStyleSheet() | ||
styleSheet.replaceSync(styles) | ||
|
||
if (styleSheetMap[id]) { | ||
document.adoptedStyleSheets = document.adoptedStyleSheets.filter((sheet) => sheet !== styleSheetMap[id]) | ||
} else { | ||
styleSheetMap[id] = styleSheet | ||
} | ||
document.adoptedStyleSheets.push(styleSheetMap[id]) | ||
} | ||
} | ||
|
||
/** | ||
* 编译sfc源文件,输出编译后的js源代码 | ||
*/ | ||
|
||
const transformVueSFC = ({ code, name }) => { | ||
const { descriptor, errors } = compiler.parse(code, { name }) | ||
if (errors.length) { | ||
throw new Error(errors.toString()) | ||
} | ||
|
||
// 获取唯一id | ||
const id = generateID() | ||
const hasScoped = descriptor.styles.some((e) => e.scoped) | ||
const scopeId = hasScoped ? `data-v-${id}` : undefined | ||
|
||
// 编译js | ||
const script = compiler.compileScript(descriptor, { id, sourceMap: true }) | ||
script.content = joinMap(script.content, script.map) | ||
|
||
// 模板编译项 | ||
const templateOptions = { | ||
id, | ||
source: descriptor.template.content, | ||
filename: name, | ||
scoped: hasScoped, | ||
slotted: descriptor.slotted, | ||
compilerOptions: { | ||
mode: 'module', | ||
inline: false, | ||
bindingMetadata: script.bindings | ||
} | ||
} | ||
|
||
const template = compiler.compileTemplate({ ...templateOptions, sourceMap: true, inlineTempate: true }) | ||
if (template.map) { | ||
template.map.sources[0] = `${template.map.sources[0]}?template` | ||
template.code = joinMap(template.code, template.map) | ||
} | ||
|
||
generateCssInJs(descriptor, id) | ||
|
||
// 将模板、js结合到一起 | ||
const moduleCode = ` | ||
import script from '${getBlobURL(script.content)}'; // script content | ||
import { render } from '${getBlobURL(template.code)}'; // template code | ||
script.render = render; | ||
${name ? `script.__file='${name}';` : ''} | ||
${scopeId ? `script.scopeId='${scopeId}';` : ''} | ||
export default script; | ||
` | ||
return moduleCode | ||
} | ||
|
||
// 循环解析嵌套区块 | ||
const handleBlock = ({ code, childBlocks }, blockList) => { | ||
let result = code | ||
childBlocks.forEach((item) => { | ||
const block = blockList[item] | ||
|
||
// 如果子区块中也有子区块,则循环调用 | ||
if (!block.childBlocks) { | ||
block.code = handleBlock({ code: block.code, childBlocks: block.childBlocks }, blockList) | ||
} | ||
|
||
// 拿到子区块的BlobURL,然后将源码中的子区块文件名换成子区块的BlobURL | ||
if (!blocksMap[block.name]) { | ||
blocksMap[block.name] = getBlobURL(transformVueSFC(block)) | ||
} | ||
|
||
const blobURL = blocksMap[block.name] | ||
result = result.replaceAll(`./${item}.vue`, blobURL) | ||
}) | ||
|
||
return result | ||
} | ||
|
||
export const generateBlock = async ({ code, name, childBlocks }, blockList) => { | ||
let blockCode = code | ||
|
||
if (childBlocks) { | ||
blockCode = handleBlock({ code, childBlocks }, blockList) | ||
} | ||
|
||
if (!blocksMap[name]) { | ||
blocksMap[name] = getBlobURL(transformVueSFC({ code: blockCode, name })) | ||
} | ||
|
||
const blockBlobURL = blocksMap[name] | ||
const AppModule = await import(blockBlobURL) | ||
return AppModule.preventDefault() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './utils.js' | ||
export * from './compiler-sfc.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// 获取对应字符串的base64格式 | ||
export const toBase64 = (str) => { | ||
return btoa(unescape(encodeURIComponent(str))) | ||
} | ||
|
||
export const generateID = () => { | ||
return Math.random().toString(36).slice(2, 12) | ||
} | ||
|
||
// 将一段js,转化为一个URL对象,注意观察浏览器的产物 | ||
export const getBlobURL = (jsCode) => { | ||
const blob = new Blob([jsCode], { type: 'text/javascript' }) | ||
return URL.createObjectURL(blob) | ||
} | ||
|
||
export const hasChildrenBlock = (code) => { | ||
return /from ['|"].\//.test(code) | ||
} | ||
|
||
export const joinMap = (content, map) => { | ||
return map | ||
? `${content}\n//# sourceMappingURL=data:application/json;base64,${toBase64(JSON.stringify(map))}` | ||
: content | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Copyright (c) 2023 - present TinyEngine Authors. | ||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. | ||
* | ||
* Use of this source code is governed by an MIT-style license. | ||
* | ||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, | ||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR | ||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. | ||
* | ||
*/ | ||
|
||
import { defineConfig } from 'vite' | ||
import path from 'node:path' | ||
import vue from '@vitejs/plugin-vue' | ||
import vueJsx from '@vitejs/plugin-vue-jsx' | ||
|
||
export default defineConfig({ | ||
plugins: [vue(), vueJsx()], | ||
publicDir: false, | ||
build: { | ||
lib: { | ||
entry: path.resolve(__dirname, './src/index.js'), | ||
fileName: () => 'index.js', | ||
formats: ['es', 'cjs'] | ||
}, | ||
resolve: { | ||
alias: {} | ||
} | ||
} | ||
}) |