From aec35a1d51bb72649dac817547bcf5d0936324db Mon Sep 17 00:00:00 2001 From: dntzhang Date: Fri, 8 Mar 2019 09:24:46 +0800 Subject: [PATCH] update tutorial --- tutorial/omip-h5.md | 141 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/tutorial/omip-h5.md b/tutorial/omip-h5.md index ea89ca9d4..a641d4617 100644 --- a/tutorial/omip-h5.md +++ b/tutorial/omip-h5.md @@ -1,4 +1,4 @@ -# Omi 多端开发之 - omip 适配 h5 +# Omi 多端开发之 - omip 适配 h5 原理揭秘 Omip 是腾讯 Omi 团队开发的跨端开发工具集,支持小程序和 H5 SPA,最新的 omip 已经适配了 h5,如下方新增的两条命令, @@ -15,16 +15,24 @@ npm run build:h5 //发布 h5 > 也支持一条命令 `npx omi-cli init-p my-app` (npm v5.2.0+) -不仅可以一键生成小程序,还能一键生成 h5 SPA。怎么做到的?下面来一一列举难点,逐个击破。 +当然也支持 TypeScript: + +```js +omi init-p-ts my-app +``` + +TypeScript 的其他命令和上面一样。 + +Omip 不仅可以一键生成小程序,还能一键生成 h5 SPA。怎么做到的?下面来一一列举难点,逐个击破。 --- ## 工作列表 * CSS rpx 转换问题 +* app.css 作用域问题 * JSX 里的小程序标签映射 * wx api 适配 * 集成路由 -* app.css 作用域问题 ## CSS rpx 转换问题 @@ -40,9 +48,128 @@ function rpx(str) { } ``` +## app.css 作用域问题 + +Shadow tree 与 omi 不一样,omi 是从根开始 shadow root,而小程序是从自定义组件开始,omio 则没有 shadow root。 + +| | **omi** | **omio** | **小程序**| +|-|-|-| +| Shadow DOM| 从根节点开始 | 无 |从自定义组件开始| +| Scoped CSS| 从根节点开始局部作用域,浏览器 scoped| 从根节点开始局部作用域(运行时 scoped)| 自定义组件局部作用域| + +所以,app.css 需要污染到 page 里的 WXML/JSX,在 omi 中怎么做? + +先看 app.js 源码: + +```js +import './app.css' //注意这行!!! +import './pages/index/index' +import { render, WeElement, define } from 'omi' + +define('my-app', class extends WeElement { + + config = { + pages: [ + 'pages/index/index', + 'pages/list/index', + 'pages/detail/index', + 'pages/logs/index' + ], + window: { + backgroundTextStyle: 'light', + navigationBarBackgroundColor: '#fff', + navigationBarTitleText: 'WeChat', + navigationBarTextStyle: 'black' + } +``` + +上面是使用 omip 开发小程序的入口 js 文件,也是 webpack 编译的入口文件,在 cli 进行语法树分析的时候,可以拿到 import 的各个细节,然后做一些变换处理,比如下面 ImportDeclaration(即 import 语句) 的处理: + +```js +traverse(ast, { + ImportDeclaration: { + enter (astPath) { + const node = astPath.node + const source = node.source + const specifiers = node.specifiers + let value = source.value + //当 app.js 里 import 的文件是以 .css 结尾的时候 + if(value.endsWith('.css')){ + //读取对应 js 目录的 css 文件,移除 css 当中的注释,保存到 appCSS 变量中 + appCSS = fs.readFileSync(filePath.replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '') + //移除这里条 import 语句 + astPath.remove() + return + } +``` + +得到了 appCSS 之后,想办法注入到所有 page 当中: + +```js + traverse(ast, { + ImportDeclaration: { + enter (astPath) { + const node = astPath.node + const source = node.source + let value = source.value + const specifiers = node.specifiers + //当 import 的文件是以 .css 结尾的时候 + if(value.endsWith('.css')){ + //读取对应 js 目录的 css 文件,移除 css 当中的注释,保存到 css 变量中 + let css = fs.readFileSync(filePath.replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '') + //page 注入 appCSS + if(filePath.indexOf('/src/pages/') !== -1||filePath.indexOf('\\src\\pages\\') !== -1){ + css = appCSS + css + } + //把 import 语句替换成 const ___css = Omi.rpx(.....) 的形式! + astPath.replaceWith(t.variableDeclaration('const',[t.variableDeclarator(t.identifier(`___css`),t.callExpression(t.identifier('Omi.rpx'),[t.stringLiteral(css)]),)])) + return + } + ... +``` + +这就够了吗?不够!因为 ___css 并没有使用到,需要注入到 WeElement Class 的静态属性 css 上,继续 ast transformation: + +```js +const programExitVisitor = { + ClassBody: { + exit (astPath) { + //注入静态属性 const css = ___css + astPath.unshiftContainer('body', t.classProperty( + t.identifier('static css'), + t.identifier('___css') + )) + } + } + } +``` + +编译出得 page 长这个样子: + +```js +import { WeElement, define } from "../../libs/omip-h5/omi.esm"; + +const ___css = Omi.rpx("\n.container {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n padding: 200rpx 0;\n box-sizing: border-box;\n} \n\n.userinfo {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.userinfo-avatar {\n width: 128rpx;\n height: 128rpx;\n margin: 20rpx;\n border-radius: 50%;\n}\n\n.userinfo-nickname {\n color: #aaa;\n}\n\n.usermotto {\n margin-top: 200px;\n}"); + +const app = getApp(); + +define('page-index', class extends WeElement { + static css = ___css; + + data = { + motto: 'Hello Omip', + userInfo: {}, + hasUserInfo: false, + canIUse: wx.canIUse('button.open-type.getUserInfo') +... +... +``` + +大功告成! + ## 标签映射 -由于小程序里的一些标签在浏览器中不能够识别,需要转换成浏览器识别的标签,所以这里列了一个映射表: +由于小程序里的一些标签在浏览器中不能够识别,比如浏览器不识别 view、text 等标签,需要转换成浏览器识别的标签,所以这里列了一个映射表: ```js const mapTag = { @@ -97,15 +224,15 @@ function h(nodeName, attributes) { | **wx** | **web** | |-|-| | wx.request| XMLHttpRequest | -| 界面 api| 实现对应的omi组件 | +| 界面 api(confirm、loaing、toast等)| 实现对应的omi组件 | | 数据存储 api| localStorage | -### 生命周期处理 +wx 特有的 api 还包括一些特有的生命周期函数,如: * onShow * onHide -这是 wx 里 Page 里的生命周期,而 omi 是不包含的。这里需要在 router 的回调函数中进行主动调用。 +这是 wx 里 Page 里的生命周期,而 omi 是不包含的。这里需要在 router 的回调函数中进行主动调用。具体怎么出发且看路由管理。 未完待续..