前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OMI 在线互动教程上线,趣味学习 Web Components

OMI 在线互动教程上线,趣味学习 Web Components

作者头像
腾讯开源
发布2022-08-26 12:40:54
6900
发布2022-08-26 12:40:54
举报
文章被收录于专栏:腾讯开源的专栏

可以通过 omijs.org 

或者 https://tencent.github.io/omi/ 找到入口。

动机

随着 IE 浏览器离我们远去,Web Components 的在浏览器端支持率越来越高。比如主流浏览器的新版本都支持: Safari 10+, IE 11+, Chrome, Firefox 和 Edge。来自大公司基于 Web Components 的框架有 google 的 Lit、microsoft 的 fast 以及 Tencent 的 OMI 等。

OMI 框架

OMI 是前端跨框架框架,您可以使用 JSX/TSX 编写标准的 Web Components 的自定义元素(Custom Elements),通过自定义元素,Web 开发人员可以创建新的 HTML 标记,增强现有HTML标记,或者扩展其他开发人员编写的组件,然后像使用 HTML 标签一样使用他们,比如:

代码语言:javascript
复制
const yourEl = document.createElement('your-omi-element')document.querySelector('your-omi-element').addEventListener('event-name', eventHandler)document.body.appendChild(yourEl)

也可以直接在 html 中使用:

代码语言:javascript
复制
<your-omi-element></your-omi-element>

除了定义标准的自定义标签,您也可以使用 OMI 构建整个应用程序。

代码语言:javascript
复制
import { tag, WeElement, h, render } from 'omi'import './your-omi-element'import './another-omi-element'
@tag('my-app')class HelloWorld extends WeElement {    render(props) {    return (      <>        <your-omi-element></your-omi-element>        <another-omi-element></another-omi-element>      </>    )  }}
render(<my-app />, 'body')

基于丰富的自定义元素开发应用程序,代码更精简,模块化、重用性更强。

在体验了各种前端框架的 playground 之后,OMI 团队也打算打造一款属于自己的 playground。第一版本我们直接使用了typescript playground 二次开发,最后效果如下所示:

使用下来发现有许多不便利的地方:

  • 没有文档辅助对新生还是不够友好
  • 多文件打包不支持
  • Monaco Editor 太重

所以我们打算从零开始重写 playground,使用 OMI 从零开发 OMI 互动教程站点

如下图所见是我们的第二个版本,OMI 互动教程站点本身就是使用 OMI 框架开发。使用到了 @omiu/tree@omiu/tabs@omiu/toast@omiu/icon@omiu/link 和 @omiu/toast,可以在 https://github.com/Tencent/omi 找到源代码。

你可以直接在右上角修改 TSX/TS/CSS 代码,右下角的预览界面可以实时看到执行效果。

原理

  • 整个站点技术栈 OMI OMIU,官方OMI组件 omi-router,官方OMI路由 omi-twind,Tailwind CSS 的 JS 版本 CodeMirror,代码编辑器 markdown-it + prismjs,markdown 文章渲染,文章内代码高亮
  • 使用 TypeScript(browser) + Rollup(browser) 在浏览器中实时编译打包
  • 使用 Vite 对站点进行启动开发和打包构建产物

下面展开详细分析下技术要点和细节。

在线编译 TypeScript

在浏览器中,直接使用 ts.transpileModule,对 TS 或 TSX 文件进行编译:

代码语言:javascript
复制
import * as ts from "typescript"
export function tsBuild(code) {  return ts.transpileModule(code, {    compilerOptions: {      module: ts.ModuleKind.ESNext,      target: ts.ScriptTarget.ESNext,      jsx: ts.JsxEmit.React,      jsxFactory: 'h',      jsxFragmentFactory: 'h.f',    }  }).outputText}

在 OMI 项目中,jsxFactory 和 jsxFragmentFactory分别对应 h 和 h.f:

代码语言:javascript
复制
<>  <div>hello</div></>

等同于:

代码语言:javascript
复制
h(h.f, null,  h("div", null, "hello")))

TypeScript(browser) 只编译单个文件,文件之间的依赖打包我们借助于 Rollup(browser)。

在线打包模块

Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。

一般情况下,我们都使用 Rollup 对本地文件进行打包,但是我们需要的场景是在浏览器中实时打包,所以需要 虚拟文件 插件:

代码语言:javascript
复制
export function vfilePlugin(files) {  return {    name: 'vfile',     resolveId(source) {      if (files[source]) {        return source       }      return null     },    load(id) {      if (files.hasOwnProperty(id)) {        return files[id]       }      return null     }  }}

以上插件将拦截虚拟模块的任何导入,而不访问本地文件系统。只需要:

代码语言:javascript
复制
import { vfilePlugin } from './rollup-plugin-vfile'
const inputOptions = {  input: './index',   plugins: [vfilePlugin(files)],  // 不需要 tree shaking  treeshake: false}

支持导入 CSS String

OMI 框架使用的是 css 字符串作为组件静态样式,所以现在还剩下一件事情就是 css 字符串的导入,因为通常我们不把他和组件写在同一个文件,而是写到单独的 css 文件当中,这样书写过程中可以或者更多的提示和校正。所以这里还需要一个 rollup 插件:

代码语言:javascript
复制
export function cssStringPlugin() {  return {    name: "css-string",    transform(code, id) {      if (id.endsWith('.css')) {        return {          code: `export default ${JSON.stringify(code)};`,          map: { mappings: "" }        }      }    }  }}

rollup 输入配置就变成这样:

代码语言:javascript
复制
import { vfilePlugin } from './rollup-plugin-vfile'import { cssStringPlugin } from './rollup-plugin-css'
const inputOptions = {  input: './index',   plugins: [vfilePlugin(files), cssStringPlugin()],  // 不需要 tree shaking  treeshake: false}

这样就支持 TS、TSX 和 CSS 文件了。

在线执行

在线运行环境使用的是嵌入的 iframe 来执行动态脚本,因为部署在同样的域名下,所以可以直接进行 iframe 通讯传值。流程如下图所示:

iframe 里的关键代码如下:

代码语言:javascript
复制
<script>  var createElement = Omi.createElement;  var define = Omi.define;  var WeElement = Omi.WeElement;  var render = Omi.render;  var h = Omi.h;  var tag = Omi.tag;  var classNames = Omi.classNames;  var extractClass = Omi.extractClass;</script><script>  var script = document.createElement("script");  script.innerHTML = parent.PreviewIframeDynamicSourceCode;  document.body.appendChild(script);</script>

通过 parent.PreviewIframeDynamicSourceCode 获取父页面构建出来的脚本进行执行,没用用户修改或者路由转换都会进行 iframe 的 reload 操作。

章节分包

随着章节的增多,再加上多语言切换,每种语言都有一份资源,构建出来的 js 越来越大,所以自然想到了分包懒加载进行处理,用户在点击某一章节的时候再加载其依赖的文档和演示资源。

在尝试了 Dynamic Import 和 Glob Import 之后,遇到了种种问题。

最开始使用的 Dynamic Import:

代码语言:javascript
复制
import(`../section/${this.lan}/${section}/description.md`).then(() => {
})

在 build 模式报错: Unknown variable dynamic import

切换到 Glob Import 之后:

代码语言:javascript
复制
const modules = import.meta.glob('./dir/*.js', { as: 'raw' })

上面的代码会转换成:

代码语言:javascript
复制
const modules = {  './dir/foo.js': 'export default "foo"\n',  './dir/bar.js': 'export default "bar"\n'}

发现 as raw 方式没有进行分包。最后我们使用 fetch 方式请求资源。

代码语言:javascript
复制
const texts = await Promise.all(urls.map(async url => {  const resp = await fetch(url)  return resp.text()}))

样式和跨屏适配

使用 omi-twind 写样式,您直接使用存在的功能性 class 就可以满足所有场景,而无需编写一行自定义 CSS。使用过程同样遵循 tailwindcss 的两个概念,Utility-First 和 Mobile-First:

1.使用一组受约束的功能样式来构建复杂界面

代码语言:javascript
复制
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">  <div class="shrink-0">    <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">  </div></div>

一旦你用这种方式构建整个程序,会带来很多好处:

  • 你不是在浪费精力发明类名,变量取名是编程中最头疼的事情
  • 无需 CSS,也就没有了持续膨胀的 CSS
  • CSS是全局性的,对 CSS 修改时你永远不知道你在破坏什么。HTML 中的 class 是本地的,可随意修改不影响其他元素

2.所有样式作用于移动端,需要适配更大的屏幕的时候需要写类似md:xxx lg:xxx

代码语言:javascript
复制
<img class="w-16 md:w-32 lg:w-48" src="...">

最后看一下我们的 tsx 代码:

在pc上的效果图:

在手机上的效果图:

最后

在线互动式教程的好处是,不会很枯燥的一直阅读文档,阅读文档的同时可以在代码编辑器里修改尝试,即时得到反馈,对 api 能够有更加深入的理解,提升学习效率。

接下来,您可以:

  • 通过该站点学习 OMI 和 Web Components
  • 通过该站点的源码学习 OMI 和 Web Components
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯开源 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 动机
  • OMI 框架
  • 原理
    • 在线编译 TypeScript
      • 在线打包模块
        • 支持导入 CSS String
          • 在线执行
            • 章节分包
              • 样式和跨屏适配
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档