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
复制代码
examples 文件夹内就是组件调试的相关脚本,这个文件夹在组建开发过程当中是不须要变更的,只是定义了调试服务器的一些逻辑。并不包含真实的组件例子。
而真实的例子存放在相应组件目录下,example.vue
中引入当前目录下的 vue 组件,调试时是针对 example.vue
进行调试,由于调试组件须要模拟使用组件的场景(改变传入值,用户交互等)。
当执行 npm run dev:components
时,开发同窗会看到浏览器打开页面:
选择想要调试的组件,好比说 weex-dialog ,进入到 weex-dialog 的调试界面。
开发同窗此时修改 packages 目录中的 weex-dialog 的组件内容,会实时看到修改内容,进行调试。
另外咱们开发的组件是基于 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 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,
});
复制代码
在引用组件时使用了 .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;
复制代码
"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 模板的问题。
脚手架文件中的某些值会根据组件名的不一样而不一样,根据组件名自动生成对应的脚手架内容,更加方便开发。
组件在本地开发完成了,例子和文档都编写完毕,但不知在真实项目中使用会不会出现奇怪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
复制代码
组件发布完成了,就能够在项目中使用了,咱们从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分钟交做业,跟我一块儿学英语吧