qinyuanf / front-end-Weekly

坚持,一种可以养成的习惯
MIT License
12 stars 5 forks source link

weex 脚手架全面解读 #13

Open qinyuanf opened 4 years ago

qinyuanf commented 4 years ago

weex 脚手架全面解读

当前解读的脚手架是基于 weex 官方脚手架的升级版。

目录:

问题分析

项目特征

weex 开发平台

启动开发平台

npm run ui

开发平台部分功能说明

控制台日志输出

开发阶段针对 css 端的日志输出主要分为以下几种形式:

以上控制台提示都是针对 native 端的兼容问题,也许 H5 端显示正常,但是都需要进行 css 样式调整,避免出现不必要的兼容问题。经过多方确认后某个 css 样式已经被 weex 支持,该样式将被记录同步到 npm 私服(weex-styler 已使用自定义 loader)。

目录详解

文件目录

|-- weex-code-weiyi
    |-- .temp                       用于 webpack 打包的入口文件
    |   |-- insurance
    |   |   |-- demo
    |   |       |-- index.js        web 端入口
    |       |-- index.web.js        native 端入口
    |-- build       webpack 配置文件
    |   |-- webpack.common.conf.js  通用配置
    |   |-- webpack.dev.conf.js     开发环境配置
    |   |-- webpack.prod.conf.js    生产环境配置
    |-- config                      环境变量配置
    |   |-- weex.config.dev.js      开发    
    |   |-- weex.config.prod.js     生产
    |   |-- weex.config.test.js     测试
    |   |-- index.js
    |-- dist        打包文件目录
    |   |-- insurance
    |       |-- demo
    |           |-- index.js        用于 native 端解析
    |           |-- index.web.js    用于 web 端解析
    |   |-- weex-zip                压缩包目录,用于上传到 kano
    |       |-- insurance-demo-index.zip        压缩了 index.js 和 index.web.js
    |-- plugins                     插件目录,暂未使用
    |   |-- plugins.json
    |-- server                      weex 工作台服务器
    |-- src                         业务文件目录
    |   |-- api                     api 接口相关
    |   |   |-- modules             api 模块
    |   |       |-- insurance       
    |   |       |   |-- demo.js
    |   |   |-- gateway-api.js      请求方法封装
    |   |   |-- index.js
    |   |-- lib                     工具方法
    |   |-- modules                 weex 模块 H5 实现
    |   |   |-- log
    |   |   |-- monitor
    |   |   |-- nav
    |   |   |-- share
    |   |   |-- user
    |   |   |-- index.js
    |   |-- pages                   weex 页面
    |       |-- components
    |       |-- insurance
    |       |   |-- demo
    |       |       |-- components
    |       |       |-- index.vue
    |   |-- entry.js                入口模板,用于生成 .temp 文件下 web 端和 native 端入口
    |-- test                        测试用例
    |-- web                         weex 工作台前端页面
    |-- .babelrc                    babel 配置文件
    |-- .eslintignore               eslint 忽略的文件目录
    |-- .eslintrc.js                eslint 配置规则
    |-- .gitignore                  git 忽略的文件目录
    |-- .gitlab-ci.yml              git-ci 运行文件
    |-- .postcssrc.js               postcss配置文件
    |-- android.config.json         android 配置,暂未使用
    |-- ios.config.json             ios 端配置,暂未使用
    |-- config.json                 项目配置文件,重要!!!用于修改全局配置项
    |-- webpack.config.js           根据当前环境判定,最终输出的 webpack 配置项
    |-- package.json                
    |-- README.md

package.json

解读一个项目结构最好的入口就是 package.json,代码如下:

{
  "scripts": {
    // 建立缓存并启动 weex 开发平台
    "ui": "npm run cache && node ./server/app.js",
    // 开发模式使用全量打包
    "dev": "cross-env NODE_ENV=development webpack-dev-server --progress",
    // 生产环境使用全量打包
    "build:prod": "cross-env NODE_ENV=production webpack",
    // 测试环境使用全量打包
    "build:test": "cross-env NODE_ENV=test webpack",
    // 开发环境使用单页打包
    "dev:single": "cross-env NODE_ENV=development PACKAGE_PATH=insurance/demo/index webpack-dev-server --progress",
    // 生产环境使用单页打包
    "build:prod:single": "cross-env NODE_ENV=production PACKAGE_PATH=insurance/demo/index webpack",
    // 测试环境使用单页打包
    "build:test:single": "cross-env NODE_ENV=test PACKAGE_PATH=insurance/demo/index webpack",
   // 创建数据缓存,用于某些全局配置
    "cache": "node ./server/utils/create-cache.js"
  }
}
cross-env

用于抹平不同平台(如 windows等)设置环境变量方式的差异,cross-env 之后 webpack/webpack-dev-server 之前即在设置环境变量。

环境变量

环境变量完全可以自定义,当前使用以下变量:

命令参数

参考:https://webpack.docschina.org/api/cli/#%E5%B8%B8%E7%94%A8%E9%85%8D%E7%BD%AE

webpack.config.js

通过 package.json 设置的环境变量 NODE_ENV 来区分当前环境应该加载哪个打包配置文件,代码如下:

let webpackConfig
module.exports = () => {
  switch (process.env.NODE_ENV) {
    case 'prod':
    case 'production':
      webpackConfig = require('./build/webpack.prod.conf')
      break
    case 'test':
    case 'testing':
      webpackConfig = require('./build/webpack.test.conf')
      break
    case 'dev':
    case 'development':
    default:
      webpackConfig = require('./build/webpack.dev.conf')
  }
  return webpackConfig
}

分别查看三个文件的导出,如下:

// webpack.dev.conf
module.exports = new Promise((resolve, reject) => {
  // ...省略了很多代码
  resolve(webConfig)
})
// webpack.test.conf
module.exports = [weexConfig, webConfig]
// webpack.prod.conf
module.exports = [weexConfig, webConfig]

疑问:命令行 webpack 能加载几种类型的配置文件?

三种。

// 导出为一个函数,如下代码
module.exports = function(env, argv) {
  return {
    mode: env.production ? 'production' : 'development',
    devtool: env.production ? 'source-maps' : 'eval'
  }
}
// 导出一个 Promise
module.exports = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        entry: './app.js',
        /* ... */
      })
    }, 5000)
  })
}
// 导出多个配置对象
// 作为导出一个配置对象/配置函数的替代,你可能需要导出多个配置对象(从 webpack 3.1.0 开始支持导出多个函数)。当运行 webpack 时,所有的配置对象都会构建。
module.exports = [{
  output: {
    filename: './dist-amd.js',
    libraryTarget: 'amd'
  },
  name: 'amd',
  entry: './app.js'
}, {
  output: {
    filename: './dist-commonjs.js',
    libraryTarget: 'commonjs'
  },
  name: 'commonjs',
  entry: './app.js'
}]

参考:https://webpack.docschina.org/configuration/configuration-types/

.temp 文件夹与 src/entry.js

按照思路,解析完 webpack.config.js 之后应该分别解读各个环境下 webpack 的配置文件,但这里先看一下 weex 的整体架构思路。

Weex 主要用于编写多页的应用程序,每个页面都对应了原生开发中的 View 或者 Activity,并且保持自己的上下文。

从 weex 官方文档来看,weex 是一个多页应用,区别于普通的 spa 及 ssr 应用,也正是这个特性,webpack 的打包方式也有所不同,优化方式也与之前完全不同。接下去的分析也会逐渐印证 weex 是个多页应用。

我们从入口开始分析,src/entry.js 作为入口模板,用于生成 .temp 文件下 web 端入口,代码如下:

entry.js

import Vue from 'vue'
import weex from 'weex-vue-render'
import initModules from '@/modules/index'
import initRouteQuery from '@/lib/query'

// 挂载 Vue
weex.init(Vue)

// 初始化自定义模块,如 log、nav 等
initModules(weex)

// 初始化路由参数模块
// 将路由参数挂载到 weex.config
initRouteQuery(weex)

.temp/**/index.web.js

import Vue from 'vue'
import weex from 'weex-vue-render'
import initModules from '@/modules/index'
import initRouteQuery from '@/lib/query'

// 挂载 Vue
weex.init(Vue)

// 初始化自定义模块
initModules(weex)

// 初始化路由参数模块
initRouteQuery(weex)

// 以下代码是与 entry.js 的差异之处
// .vue 的路径从文件夹目录读取
// 由此可见,weex 项目是多页的,每个入口文件对应一个路径
const App = require('../../../src/pages/insurance/demo/index.vue')
new Vue(Vue.util.extend({el: '#root'}, App))

.temp/**/index.js

// h5 端所需要的自定义模块及参数模块,在 app 端都有同步实现
// 因此 native 的入口文件相较于 h5 端少很多东西
import App from '../../../src/pages/insurance/demo/index.vue'
App.el = '#root'
new Vue(App)

build 文件夹

该文件夹主要放置与 webpack 构建相关的配置文件及工具方法。

webpack.dev.conf/webpack.test.conf/webpack.prod.conf 从这三个文件中可以发现都引用了 webpack.common.conf.js,所以从该文件开始看起。查看详情

webpack.common.conf.js

经过 vue-loader的 postTransformNode image 标签被渲染为 AST。

{ 
  type: 1,
  tag: 'image',
  attrsList: [ { name: 'src', value: 'https://vuejs.org/images/logo.png' } ],
  attrsMap: { style: 'width:500px;height:500px', src: 'https://vuejs.org/images/logo.png' },
  rawAttrsMap: {},
  parent: { 
    type: 1, tag: 'div', attrsList: [ [Object] ], attrsMap: { class: 'insurance-demo' },
    rawAttrsMap: {}, parent: undefined, children: [ [Object], [Object], [Circular] ]
  },
  children: [],
  ns: 'svg',
  plain: false,
  staticStyle: '{"width":"500px","height":"500px"}',
  attrs: [ { name: 'src', value: '"https://vuejs.org/images/logo.png"', dynamic: undefined } 
}

然后再通过 weex-vue-precompiler,在原来 AST 的基础上将 weex 特有的组件,如 text、image 等进一步转化为对应的 AST。

{ 
  type: 1,
  tag: 'figure',
  attrsList: [Array],
  attrsMap: [Object],
  rawAttrsMap: {},
  parent: [Circular],
  children: [],
  plain: false,
  staticStyle: '{"width":"6.66667rem","height":"6.66667rem"}',
  attrs: [Array],
  _origTag: 'image',
  _weexBuiltIn: true,
  staticClass: '" weex-el weex-image"' } ],
  plain: false,
  staticClass: '"insurance-demo"'
}
webpack.dev.conf.js

开发阶段的 webpack 配置文件,主要作用:

web 端

native 端

webpack.prod.conf.js/webpack.test.conf.js

生产阶段和测试阶段的配置文件,主要区别只有环境变量,主要作用:

native 端

web 端

src/modules 文件夹

h5 端实现自定义模块,这些模块在 app 端会由原生提供支持;推荐进行判空处理,万一 h5 端未实现,调试阶段会报错。

config.json 文件

项目配置文件,变量说明

简述 weex 工作机制

先看一张图:

从图中我们可以大致总结一下 weex 的工作流程:

  1. 前端编写 vue 单文件,通过 webpack 打包一份 js-bundle(上文已经比较透彻得讲述该文件在前端是如何被打包的),然后该 js-bundle 会通过上传工具(如 weex-manager)被上传到文件服务器(如 kano),文件信息被服务器(如 weex-renderer) 处理后保存到数据库;

  2. 打开 app 时,客户端会定时去请求服务器(如 weex-renderer),服务器会告诉 app 是否有新的 js-bundle 需要下载,如果需要则返回下载链接,客户端下载新的文件到本地并替换对应文件;

  3. 当打开一个页面时,客户端提供了 js 执行引擎(V8/js core,作用相当于浏览器),用于执行缓存在本地的 js-bundle;

  4. 在执行 js-bundle 之前,weex 执行引擎(v8/js core)还将加载 weex-vue-framework 框架,相当于在 weex 环境执行的 vue.js,区别在于 vue.js 最终生成了 dom,而该框架最终生成了 native dom;

  1. 有了 native dom,接着通过 WXBridge 发送渲染指令到 native(ios、android),最终 native 渲染引擎完成最终的页面渲染

问题解答

分析下当前所使用 Hybrid 方案:

  1. app 内利用 webview 在线加载 h5 页面,h5 使用了 ssr 服务;

    • 优点:写一套代码能够同时运行在浏览器端(微信等)、app 端,开发效率高,对 app 侵入小;
    • 缺点:app 内在线加载 h5 页面,渲染效率还是强依赖网络,首屏加载慢,一段时间观察下来体验堪忧。
  2. app 内置离线包(如首页)

    • 优点:hybrid 方案的正确打开姿势,所需要加载的页面提前下载,首屏加载快,可离线使用;
    • 缺点:需要一套完善的 h5 页面热更新机制,开发、调试成本相对较高,需要多端合作,同一需求可能需要两套代码去支持浏览器端和 app 端。
  3. weex

    • 优点:代码提前下载,js-bundle 加载速度快,原生系统渲染,体验接近原生,却拥有和 h5 一样的迭代速度;
    • 缺点:编写代码不能随心所欲,需要遵守一定的规范;参考资料少,社区不活跃;调试和排查问题的成本较高。
  4. 原生(不属于 hybrid 体系)

    • 优点:体验好,丝般顺滑;
    • 缺点:开发周期长,版本审核周期长,一个需求得折腾一个月,无法满足当前日新月异的需求变化。

众所周知 app 内部 weex 页面的体验明显好于 h5,而在微信端或浏览器端 ssr 的效果要好于 spa。从用户角度出发,每次需求评审时要明确该需求在哪一端更有用户量。如果产品强调 seo,需要更快地首屏加载速度,会有大量合作方需求,那 ssr 是必要的;相反,产品更看重 app 内部的用户体验,需求都以 app 为主,h5 端的页面只要在某个活动时出现,此时 weex 的三端统一方案或许更合适。

展望

作为一个项目,我们追求 h5 的迭代效率和原生的用户体验。作为一个脚手架,我们希望能够与时俱进,拥有更好的开发体验,更快的打包效率,在某个项目中使用了我们脚手架,如何保持脚手架的持续迭代可能会是接下去主要的尝试方向。。。

iOSBoy commented 2 years ago

你好 非常感谢的文章 写的非常好 请问怎么联系你呢