尚妆达人店 UI 组件化 工程实践(weex vue)

19年目标:消灭英语!我新开了一个公众号记录一个程序员学英语的历程javascript

有提高英语诉求的小伙伴能够关注公众号:csenglish 程序员学英语,天天花10分钟交做业,跟我一块儿学英语吧html

前言

尚妆大前端团队使用 weex 进行三端统一开发有一段时间了,截止本文发表「达人店」APP大部分页面都已经用 weex 进行了重构,在此期间也积累了一些基础组件和业务组件。前端

以前维护组件的方式是在达人店项目的工程内维护一个 components 文件夹,随平常开发迭代,并行需求与开发人员的增多,这种维护方式也暴露出一些问题。vue

一、开发人员能够随意跟随需求开发修改 components 内的组件,破坏约定好的规范,或埋入 bug。java

二、定义组件缺乏规范,好比在某个需求开发中, A 开发人员以为这个功能能够抽离成组件,就直接在 components 内定义并使用,但实际倒是伪需求,用了一次就再也没有人使用,形成 components 组件库的部分冗余。node

三、组件抽离过程没法协同使用,好比 A 开发同窗切了个特性分支 feature/A,并根据项目抽了个通用组件 ComponentA,B 开发切了个特性分支 B,也想使用这个 ComponentA 组件,但此时两人在不一样分支,代码并不能共享。android

四、。。。webpack

基于上述不便之处,咱们尝试将 components 抽离出来,放到内部私有 npm 仓库中以 npm 包的形式去维护。git

也就是咱们将 spon-ui(内部组件库名称)做为单独的一个项目去维护,加以约束造成组件库开发规范,能有效的解决上述问题。程序员

此文就是这次抽离过程的一些实践,包含了组件的调试文档调试npm使用组件 发布等内容。固然 weex 的语法同 vue,这些实践也一样适用于 vue。

一、组件库的调试

先看下 spon-ui 组件库项目的目录结构。

|- spon-ui
||-- build
||-- docs
||-- examples
||-- packages
|||--- weex-field
||||---- index.js
||||---- field.vue
||||---- example.vue
||||---- readme.md
||||---- package.json
||-- src
复制代码
  • build 中存放一些脚本执行文件,用于工程的调试、发布。
  • docs 中存放文档调试的脚本,生成一个文档调试服务器。
  • examples 中存放组件调试的脚本,生成一个组件调试服务器。(不存放组件例子)
  • packages 存放真实组件,以及组件的文档和例子。
  • src 存放组件可使用的公共方法。

组件的调试

examples 文件夹内就是组件调试的相关脚本,这个文件夹在组建开发过程当中是不须要变更的,只是定义了调试服务器的一些逻辑。并不包含真实的组件例子。

而真实的例子存放在相应组件目录下,example.vue 中引入当前目录下的 vue 组件,调试时是针对 example.vue 进行调试,由于调试组件须要模拟使用组件的场景(改变传入值,用户交互等)。

当执行 npm run dev:components 时,开发同窗会看到浏览器打开页面:

选择想要调试的组件,好比说 weex-dialog ,进入到 weex-dialog 的调试界面。

开发同窗此时修改 packages 目录中的 weex-dialog 的组件内容,会实时看到修改内容,进行调试。

console 中输出二维码

另外咱们开发的组件是基于 weex 的,意味着开发的组件须要支持三端(iOS android H5),因此在 console 中会打印当前组件js的二维码,用于 native 调试。

如何在console中输出二维码也是个小trick,首先利用js的二维码库将资源生成二维码图,而后利用console输出背景图的机制打印二维码。

console.log("%c", "padding:75px 80px 75px;line-height:160px;background:url(" + base64 + ") no-repeat;background-size:160px");
复制代码

整个调试页面是经过单页面的形式展示的,使用 vue-router 进行路由控制,weex 也支持 vue-router ,因此这个单页面在 native 中也能良好运行。

自动生成组件相关信息

在每次执行 npm run dev:components 命令时,会根据 packages 目录下的组件自动生成 nav-list.js 文件,这个索引文件用来定义 vue-router 的路由信息,以及调试主页的组件列表。这样作能够彻底将调试过程抽离成黑盒,开发人员只需关注 packages 目录下的开发便可。

const routes = navList.map((item) => {
  const path = item.path;
  return {
    path,
    // 须要加vue后缀,否则webpack会将examples下的全部文件都require一下
    component: require('examples/' + item.exampleRequire + '.vue'),
  };
});
routes.push({
  path: '/',
  component: require('./app.vue'),
})
复制代码
// 组件列表也经过 nav-list.js 渲染
<spon-cell-group>
   <spon-cell v-for="(page, jndex) in item.list" :key="jndex" :title="page.title" :is-link="true" @click="changePage(page)" ></spon-cell> </spon-cell-group> 复制代码

webpack require 动态的资源

本文使用 webpack 3.x.x

上节提到的 require 动态的模块时,若是不代表文件类型,webpack会将该目录下全部资源都 require 一遍,形成的问题是若是目录下有某类型的文件,而又没有使用对应的loader,在编译过程就会报错。上节中若是不加 .vue 后缀, webpack会将 examples 目录下全部资源都require一遍。

因此在定义各路由的component时,须要加上 vue 后缀,查找vue文件。

component: require('examples/' + item.exampleRequire + '.vue'),
  };
复制代码

webpack的文档说明在 webpack.js.org/guides/depe…

在 webpack 的官方文档里列出了动态 require 的原理,对于 require("./template/" + name + ".ejs"); 含表达式的引用,webpack 解析此处的 require,获得两个信息:

一、 目录为 ./template 二、匹配规则为 /^.*\.ejs$/

而后 webpack 会根据这两个信息获得一个 context module,这个模块包含了 ./template 目录下全部以 .ejs 为后缀的模块。

{
    "./table.ejs": 42,
    "./table-row.ejs": 43,
    "./directory/folder.ejs": 44
}
复制代码

还有一个 require.context() 方法能够自定义动态引用的规则,文档中也有示例,官网给出了一个基于此的demo,引入一个目录中全部符合规则的模块。

function importAll (r) {
  r.keys().forEach(r);
}

importAll(require.context('../components/', true, /\.js$/));
复制代码

文档的调试

组件开发的差很少了,就要编写相应的文档,方便同事小伙伴使用,执行 npm run dev:docs 会开启文档调试服务器,方便开发同窗编写文档。

文档服务器的逻辑放在 docs 目录下,一样与组件代码解耦,左侧的组件信息动态取自 packages 目录下的组件信息,右侧的组件预览直接使用 examples 目录下的组件调试逻辑,中间的部分取自 组件中的 readme.md 文件。

整个文档应用也是基于 vue + vue-router 开发。

<div class="nav-bar-container">
    <page-nav></page-nav> </div>

<div class="document-area-container markdown-body">
    <router-view></router-view> </div>

<div class="mock-phone-container">
    <page-preview :component-name="componentName"></page-preview> </div>
复制代码

<router-view> 就是对应的路由所展现的文档内容,相应的在定义路由信息时须要肯定路由以及路由所对应的 readme.md 路径。

const routes = navList.map((item) => {
  const path = item.path;
  return {
    path,
    component: require('mds/' + item.mdRequire + '.md'),
  };
});

const router = new VueRouter({
  routes,
});
复制代码

markdown 转换 vue

在引用组件时使用了 .md 后缀,这里是采用了 vue-markdown-loader 饿了么出品的loader。这个loader仍是借助vue-loader,首先会将 md 的内容转换成 html ,而后再转换成 vue 所须要的单文件形式给vue-loader。

var renderVueTemplate = function(html, wrapper) {
  // 本文做者注
  // 传入的html是根据 markdown插件将md转换而来
  var $ = cheerio.load(html, {
    decodeEntities: false,
    lowerCaseAttributeNames: false,
    lowerCaseTags: false
  });

  ...
  // 本文做者注
  // 将html转换成 vue-loader 所需的字符串形式
  result =
    `<template><${wrapper}>` +
    $.html() +
    `</${wrapper}></template>\n` +
    output.style +
    '\n' +
    output.script;

  return result;
};
复制代码
var result =
    'module.exports = require(' +
    loaderUtils.stringifyRequest(
      this,
      '!!vue-loader!' +
        markdownCompilerPath +
        '?raw!' +
        filePath +
        (this.resourceQuery || '')
    ) +
    ');';
    
  // 本文做者注
  // 将转换好的字符串传给 vue-loader
  return result;
复制代码

二、基于 npm 脚本实现工程化

"scripts": {
    "bootstrap": "npm i",
    "dev:components": "node build/bin/dev-entry.js",
    "dev:docs": "node build/bin/docs-dev-entry.js",
    "build:docs": "node build/bin/docs-build.js",
    "pub:docs": "npm run bootstrap && npm run clean && node build/bin/release.js",
    "pub:components": "node build/bin/prepublish.js && lerna publish --skip-npm --skip-git && node build/bin/publish.js",
    "clean": "rm -rf docs/dist && rm -rf docs/deploy",
    "add": "node build/bin/add.js"
  },
复制代码

本项目中将全部经常使用的命令都进行了抽离,开发同窗使用的命令最后暴露出4个:

npm run dev:components 组件的调试
npm run dev:docs 文档的调试
npm run pub:docs 文档的发布
npm run pub:components 组件的发布
复制代码

推荐看阮一峰的博客 npm scripts 使用指南 ,将npm 脚本很细致的介绍了一遍。

自动生成脚手架

npm run add 会自动添加一个组件所需的脚手架信息,方便开发同窗添加新组件。

这里推荐使用 json-templater/string 模块处理 string 模板的问题。

脚手架文件中的某些值会根据组件名的不一样而不一样,根据组件名自动生成对应的脚手架内容,更加方便开发。

npm link

组件在本地开发完成了,例子和文档都编写完毕,但不知在真实项目中使用会不会出现奇怪bug。

最原始的方法能够将组件复制到项目中的npm包中进行真实调试。

固然 npm 也提供了 方法专门解决这种问题。

一、首先在 spon-ui 组件库的根目录执行 npm link

二、回到项目目录,执行 npm link spon-ui ,两条命令就能将项目中本来引用的spon-ui 映射到本地的spon-ui目录中去。

三、npm unlink 取消软链。

三、源码依赖

上节提到的npm 脚本并无提到组件打包的流程,由于若是在组件这层就进行打包,会增长一些webpack的冗余代码,增长字节,并且这个组件库目前彻底属于内部项目使用,打包环境在项目中就存在,没有必要提早进行打包。

因此发布出去的组件包就是packages下的全部组件,项目中所依赖的都是组件的源码,称为源码依赖

要作到源码依赖,须要修改业务项目中(非本组件项目)的babel的配置。排除掉 spon-ui 组件

module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules(?!\/.*(spon-ui).*)/,
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
          },
        },
      ],
    },
复制代码

滴滴有篇webpack 应用编译优化之路有讲到源码依赖所带来的好处。

四、发布组件

咱们使用了 lerna 来管理组件的发布,lerna 有两种发布方式,一种是一个项目的全部组件做为一个发布包,还有一种能够将一个项目中的多个组件分别发布。

咱们使用了第一种,即全部组件统一成一个发布包。这种方式发挥不出 lerna 的威力,可是做为发布前的版本号管理仍是不错的。将来若是要将各个组件单独发布,改一下配置就ok。

版本管理

测试版本的管理

在前文就提到过目前组件库的开发仍是依赖于需求的迭代,小团队没有人专门开发组件,组件的开发会跟随需求的迭代而迭代。

那么在前期组件变动需求经过评审会后,就会跟随项目正式进入开发流程。项目开发会区分测试环境和预发全量环境,那么组件的版本号也须要区分测试环境和全量环境。

npm publish --tag

介绍一下 publish 的 tag,发布的 npm 包默认会有一个 latest 标签,每次执行 npm publish 都会自动将 tag 设置为 latest,也能够理解为稳定版,因此咱们要作的是再添加一个 tag

npm publish --tag dev

这个命令表明添加一个名为 dev 的tag,并将这次发布的版本号贴上 dev 标签。

执行 npm dist-tag ls spon-ui 能够查看当前的标签所对应的版本号信息。

➜  spon-ui git:(master) npm dist-tag ls spon-ui
dev: 0.1.0-12
latest: 0.1.0
复制代码

在项目中安装spon-ui的时候,根据状况分别执行

npm i spon-ui@dev
npm i spon-ui@latest
复制代码

五、npm5 package-lock.json

组件发布完成了,就能够在项目中使用了,咱们从npm3.x更新到了npm5,可是发现执行 npm i 时的现象跟网络上的科普文不太一致。

有提到无论怎么修改package.json文件,重复执行npm i,npm都会根据lock文件描述的版本信息进行下载。

也有提到重复npm i时,npm会不顾lock的信息,根据package.json中的包Semantic versioning 版本信息下载更新模块(lock貌似没啥用了)。

查阅资料得知,自npm 5.0版本发布以来,npm i的规则发生了三次变化。

一、npm 5.0.x 版本,无论package.json怎么变,npm i 时都会根据lock文件下载

github.com/npm/npm/iss… 这个 issue 控诉了这个问题,明明手动改了package.json,为啥不给我升级包!而后就致使了5.1.0的问题...

二、5.1.0版本后 npm install 会无视lock文件 去下载最新的npm

而后有人提了这个issue github.com/npm/npm/iss… 控诉这个问题,最后演变成5.4.2版本后的规则。

三、5.4.2版本后 github.com/npm/npm/iss…

大体意思是,若是改了package.json,且package.json和lock文件不一样,那么执行npm i时npm会根据package中的版本号以及语义含义去下载最新的包,并更新至lock。

若是二者是同一状态,那么执行npm i都会根据lock下载,不会理会package实际包的版本是否有新。

总结

以上就是咱们将UI组件从项目中迁移出来单独以npm包的形式去维护的实践过程,不完美还有待时间的考验,但愿其中的一些内容能帮助到你们。

19年目标:消灭英语!我新开了一个公众号记录一个程序员学英语的历程

有提高英语诉求的小伙伴能够关注公众号:csenglish 程序员学英语,天天花10分钟交做业,跟我一块儿学英语吧

相关文章
相关标签/搜索