likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 30

OMI 互动教程可以通过 omijs.org 或者 tencent.github.io/omi/ 找到入口。

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

动机

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

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

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

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

OMI 框架

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

const yourEl = document.createElement('your-omi-element')
document.querySelector('your-omi-element').addEventListener('event-name', eventHandler)
document.body.appendChild(yourEl)

也可以直接在 html 中使用:

<your-omi-element></your-omi-element>

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

import { tag, WeElement, h, render } from 'omi'
import './your-omi-element'
import './another-omi-element'

@tag('my-app')
class MyApp 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 二次开发,最后效果如下所示:

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

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

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

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

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

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

你可以直接在右上角修改 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 文件进行编译:

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 项目中,jsxFactoryjsxFragmentFactory 分别对应 hh.f:

<>
  <div>hello</div>
</>

等同于:

h(h.f, null,
  h("div", null, "hello")))

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

在线打包模块

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

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

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 
    }
  }
}

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

import { vfilePlugin } from './rollup-plugin-vfile'

const inputOptions = {
  input: './index', 
  plugins: [vfilePlugin(files)],
  // 不需要 tree shaking
  treeshake: false
}

支持导入 CSS String

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

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

rollup 输入配置就变成这样:

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 文件了。

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

在线执行

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

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

iframe 里的关键代码如下:

<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 ImportGlob Import 之后,遇到了种种问题。

最开始使用的 Dynamic Import:

import(`../section/${this.lan}/${section}/description.md`).then(() => {

})

在 build 模式报错: Unknown variable dynamic import

切换到 Glob Import 之后:

const modules = import.meta.glob('./dir/*.js', { as: 'raw' })

上面的代码会转换成:

const modules = {
  './dir/foo.js': 'export default "foo"\n',
  './dir/bar.js': 'export default "bar"\n'
}

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

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.使用一组受约束的功能样式来构建复杂界面

<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

<img class="w-16 md:w-32 lg:w-48" src="...">

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

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

在pc上的效果图:

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

在手机上的效果图:

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

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

最后

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

接下来,您可以:

转载自:https://juejin.cn/post/7133010509359497223
评论
请登录