bigmeow

bigmeow 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/bigmeow 编辑
编辑

专治各种不懂。

个人动态

bigmeow 关注了用户 · 6月4日

yuanzm @yuanzm

Github,欢迎Follow~
https://github.com/yuanzm

关注 978

bigmeow 发布了文章 · 1月17日

前端工程化之路(1)- 使用 verdaccio 搭建私有npm库

服务端安装部署

安装

本文书写时 verdaccio 最新版本是 4.4.1
其依赖 NodeJs 8.x 及其以上python 2.7及其以上 版本,推荐都装最新的

打开终端,全局安装:

# Windows下以管理员模式打开cmd
npm install -g verdaccio
# mac 或者 linux
sudo npm install -g verdaccio

安装verdaccio

启动服务

verdaccio

运行.gif

服务启动成功后浏览器访问 http://localhost:4873/ 即可,so easy

这里需要注意的是启动时第一行的打印信息:

 warn --- config file  - C:\Users\tiany\AppData\Roaming\verdaccio\config.yaml

这里输出的 config.yaml 就是 verdaccio 的配置文件

如果你需要 verdaccio 服务在你的服务器后台运行,参照这里配置进程守护

配置

默认配置解读

用编辑器打开 config.yaml 配置文件,我对其作用进行了一些注释解读:

#
# This is the default config file. It allows all users to do anything,
# so don't use it on production systems.
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#

# 用户下载安装的包都被缓存在此配置的目录
storage: ./storage
# 插件所在目录
plugins: ./plugins

# 定制 Web 界面
web:
  # 访问服务主页时(http://localhost:4873/),网页标签的title名称,可改成公司名称
  title: JK724包管理中心
  # Gravatar 头像支持,默认关闭,可打开(http://cn.gravatar.com/)
  gravatar: true
  # 默认情况下package 是升序自然排序的, 可选值: asc 或 desc
  # sort_packages: asc

auth:
  htpasswd:
    # 存储了加密认证信息的 htpasswd 文件
    file: ./htpasswd
    # 允许注册的用户最大数量, 默认值是 "+inf",即不限制
    # 可以将此值设置为-1 以禁用新用户注册。
    # max_users: 1000

# 如果你要安装的包在私有的npm库里没有找到,就去下面的服务列表里找
uplinks:
  npmjs:
    url: https://registry.npmjs.org/

packages:
  '@*/*':
    # scoped packages
    access: $all
    publish: $authenticated
    unpublish: $authenticated
    proxy: npmjs

  '**':
    # 默认情况下所有用户 (包括未授权用户) 都可以查看和发布任意包
    #
    # 你可以指定 用户名/分组名 (取决于你使用什么授权插件,默认的授权插件是内置的 htpasswd)
    # 访问权限有三个关键词: "$all", "$anonymous", "$authenticated"
    # $all 表示不限制,任何人可访问;$anonymous 表示未注册用户可访问;$authenticated 表示只有注册用户可访问
    access: $all

    # 允许所有注册用户发布/撤销已发布的软件包
    # (注意:默认情况下任何人都可以注册)
    publish: $authenticated
    unpublish: $authenticated

    # 如果私有包服务不可用在本地,则会代理请求到'npmjs'
    proxy: npmjs

# You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough.
server:
  keepAliveTimeout: 60

middlewares:
  audit:
    enabled: true

# 终端日志输出配置
logs:
  - { type: stdout, format: pretty, level: http }
  #- {type: file, path: verdaccio.log, level: info}
#experiments:
#  支持 npm token 命令
#  token: false

以上是 verdaccio 在启动时的默认配置,完整配置还需要去官网查看文档,但对于我们来说,目前迫切需要更改的配置的用户鉴权,毕竟私有npm仓就是不想外人访问或下载,而默认情况下是任何人都可以注册和查看的的,而一旦注册就有了发包的权力

添加注册用户

verdaccio 的鉴权模块是使用 htpasswd插件 实现的,对权限的配置比较简单,不支持给某个具体的用户设置细分权限(比如访问权限、发布权限),不过对于我们公司的情况来说够用了。

  1. 添加新账户,执行完命令后会让你输入 用户名、密码

    npm adduser --registry http://localhost:4873
  2. 禁用注册。修改 config.yaml 文件,放开 max_users 选项:

    auth:
      htpasswd:
        # 存储了加密认证信息的 htpasswd 文件
        file: ./htpasswd
        # 允许注册的用户最大数量, 默认值是 "+inf",即不限制
        # 可以将此值设置为-1 以禁用新用户注册。
        max_users: -1
  3. 更改 package 权限,只有注册用户可访问、发布包:

    packages:
      '@*/*':
        access: $authenticated
        publish: $authenticated
        unpublish: $authenticated
        proxy: npmjs
    
      '**':
        access: $authenticated
  4. 重启 verdaccio 服务

修改web用户UI

config.yaml 配置中也提供了web用户界面相关UI元素的修改,logo,主题色等,具体点击这里

客户端使用

  1. 登录(如果未禁用注册,输入不存在的账号,会自动注册)

    ## 具体场景中,http://localhost:4873 改成npm私有服务器所在的ip地址
    npm adduser --registry http://localhost:4873
  2. 发布

    npm publish --registry http://localhost:4873

参考文档

https://verdaccio.org/docs/zh...

查看原文

赞 4 收藏 3 评论 0

bigmeow 关注了专栏 · 1月4日

凹凸实验室

凹凸实验室(Aotu.io,英文简称O2) 始建于2015年10月,是一个年轻基情的技术团队。 O2面向多终端技术体系,致力于构建沉淀与分享包括但不限于交互、页面制作技巧、前端开发、原生APP开发等方面的专业知识及案例。

关注 1982

bigmeow 收藏了文章 · 2019-05-29

精读《为什么专家不再关心技术细节》

1. 引言

本周的精读是有感而发。

笔者接触前端已有八年,观察了不少前端大牛的发展路径,发现成功的人都具有相似的经历:

初期技术热情极大 -> 大量标志性技术项目 -> 转向综合性思考 -> 带团队/关注方法论

也就是专家们变得越来越不关心技术细节。需要说明是的,这里说的专家不再关心细节,不代表成为专家后学不会细节,也不代表专家不了解细节。

早期挺难理解这种转变的,笔者在学校里的知名度来自于前端做得精深,一根筋钻研技术的人眼里是容不下沙子的,所以当初为一些前辈转到管理特别不理解,认为他们背叛了前端。

不过笔者的观念也在逐渐发生转变,渐渐自己也在朝着当初反感的方向发展,觉得这一定不是偶然,所以就整理了一下感悟,希望可以证明这个发展路径的必然性。

2. 精读

首先我们要明确技术员与科学家的区别,为业务提供技术支持都是技术员,所以前端是一门技术,不是科学。

另外,技术的发展需要商业推动,没有使用场景的国家是很难推动技术进步的,科学除外。

所以业务技术是具备可持续发展的路线,毕竟大家都要吃饭,有业务价值的项目会活下来,附着在业务上的技术才能活下来,才有可能开枝散叶。

本文将从三个点去解释,为什么专家看上去越来越原理技术细节。

2.1 技术细节对个人的重要性是在变化的

随着工作年限增加,技术细节重要性在慢慢降低,反之技术视野重要性在慢慢增加。

在找工作初期,技术细节是重要的敲门砖

大学毕业的那段时间,技术细节是一块重要的敲门砖,只有掌握好技术,才会有公司愿意要你。

这也是为什么说毕业生不要一进公司就谈战略,因为时机不对。

技术不是科学,普通人下功夫可以学会

学习技术不需要很聪明的头脑,只要肯下功夫,拥有不错的理解能力,任何人都可以把技术细节搞清楚。

也就是学习技术细节是没有技术门槛,随着年龄的增加,如果只累积了大家都能学会的内容,那么当旧知识被淘汰后,学习新知识的速度又不如年轻人快,会逐渐失去经验优势。

那么如何利用无门槛的特征,将其变为门槛呢?那就是任何年龄段学习技术细节都很容易,在你需要深入细节的时候再深入进去,不需要深入的时候把时间花在了解宏观架构上。

就是培养高效的学习能力,能准确判断某个技术细节是否有必要掌握,如需要该如何快速掌握核心内容,并在掌握之后不留恋,可以快速抽身出来继续全局性思考。这种思维是有门槛的,技术专家都可以做到这一点。

做成事不一定要搞懂细节

乍一看有点匪夷所思:不了解细节怎么能做成事?

虽然理解技术细节可以做成事,但做成事不一定需要理解业务细节。

这要看怎么理解业务与技术的关系,比如建设 “数据联邦”,光是了解各个不同的存储系统技术细节可能就要花很久,而实际上是没必要将所有技术细节都弄懂的,只要定好一个通用交互规范,各存储系统各自封装一套符合这个规范的交互接口即可。

做成事往往需要宏观的技术思维,需要将许多技术点链接在一起。举个例子,做成事就类似于军官指挥作战,做成的目的是通过制定打法赢得战争,而不是自己冲锋陷阵并测量敌人壕沟的宽度。关心技术细节只最终落实到每个人具体实施项中的一部分,技术细节的目标累加起来才是做成事。

2.2 搞清楚业务对技术的真实诉求

业务期望通过技术实现功能,所以技术专家要做的是如何更好的实现业务需求,这就意味着理解业务需求是第一重要的能力。试想一个不能理解业务要做什么的人,即便懂得再多技术细节,对业务也是没有价值的。

业务思维是解决问题,技术思维是创造问题

拥有技术思维的人,容易沉迷于解决不切实际的问题,或者是别人解决过的问题。这种思维对技术学习是非常有帮助的,但如果长期不能转变这种思维,对公司来说是无法创造什么价值的。

拥有业务思维的人,首先要懂业务,只有懂业务,跟着对的业务,才能对未来又信心,知道自己的付出可以换来回报。

懂业务后,才知道如何通过技术帮助业务获得成功。

比如在一家创业公司,老板的眼光很准,进入的时机较早,市场是一片蓝海。你通过分析后,发现要帮助业务占领市场,只要利用某个成熟技术框架快速迭代,就可以在短期帮助业务赢得市场。但是这个框架定制能力不强,如果新需求来了可能需要花时间重构掉。此时技术思维的人只会考虑代码维护性,提出自研一套框架,而拥有业务思维的技术专家会决定先用成熟的技术快速作出原型,等业务稳定后再重构掉。

当然现在互联网市场竞争很激烈,低技术门槛的蓝海基本已都变成了红海,上面提到的场景可能比较少见,我们更多需要决策的是未来几年内业务的收益是否值得现在投入的研发资源。

两个会写框架的人,不如一个能决策的人

另一个简单的例子就是,假如技术专家只会一头扎在技术细节里,对各种前端框架的实现了如指掌,大家都能造出优雅、易用、可维护,而且还带有各自 “特色优势” 的框架或者轮子,那么团队很容易陷入两个专家屁股决定脑袋的技术纷争中。这种情况下,两名技术专家的产出甚至不如一个实习生大,毕竟实习生直接拿来开源框架上手,99% 的情况可靠性比前端专家自己造的轮子更好。

从另一个方面来说,现阶段前端界能写出 React、Vue 框架的人太多了,已经写出来的类 React、Vue 的框架也数不过来。去掉为了练手而做的项目,真正希望推广出去给别人用的还占绝大多数,这是开源界典型的问题:重复低水平造轮子不需要理由,推广给你用也不需要负责任。由于框架属于互联网虚拟资产,边界成本为零,这决定了框架市场一定是个大寡头市场,不可能有类似的项目通过一些不痛不痒的特色分一杯羹。那么就算招 10 个会写框架的人进入公司架构组,最后只有两种可能:要么架构臃肿,每个人都把自己的一部分功劳加入进去;要么就是选择一个更不好的方案,这样不会损害任何一位架构师的利益。

所以现在公司更倾向于内部培养人才,因为内部的人了解业务需要什么,创造的价值往往比空降的架构师更大。

宽广的技术视野更容易借力

现在技术点越来越多,如果什么技术细节都要详细了解,最终一定不能有很好的全局视野。比较好的状态是找几个重点深入了解,其他的技术点在掌握了全局技术视野后再考虑深入。

在互联网初期,很多技术框架还不完善时,技术借力的意义不大,毕竟也没有多少东西可用。

但是现在无论前端还是后端的技术、轮子已经眼花缭乱了,能掌握这些已有技术的人,价值已经逐渐大于会完整了解某些技术细节的人。一个优秀的专家应该能快速定位要解决的业务问题是否有成熟的技术方案,如何以最小的投入产出比实现,同时保持良好的维护性应变业务维护。

2.3 仅仅技术好是无法成为专家的

技术专家真的代表技术壁垒很强的人吗?是的,但只有技术能力是不够的。

为什么开源项目后期要寻找协作者?

我做开源项目的初期,所有框架和源码都事必躬亲,觉得自己有更好的点子可以胜过其他框架。初期很少有贡献者参与,当然我也不愿意其他贡献者参与,毕竟他们不了解设计理念,只有我自己的修改可以让我满意。

还有谁比作者更了解他的开源项目呢?那为什么一个大型开源项目运作到后期,基本都是协作者在维护?

因为开源是一件系统化的事情,如果你想长期维护他,必须建立好文档系统,让你的思路可复制,让他人可参与。如果开源项目只有你一个人懂,那么同时维护两个、四个、六个的时候,你定会发现力不从心。

至于一些开源大神一人维护几百甚至上千 Repo,背后一定有更多的贡献者支持,一个人就算辞职在家专职做开源,也很难同时维护超过 10 个开源项目。你需要拥有开放的心态让更多人加入进来,将成就感和荣誉感分一些给贡献者,他们才会持续为项目贡献。

能够调用资源才能成为专家

开源界就是项目抢占关注度的游戏。假设开源社区总人数为 100,你的项目能够吸引到 10 个人浏览,5 个人使用,2 个人贡献,基本就能存活下来。而开源社区至少有 100 个项目,社区总人数不足以支持每一个项目,只有获得足够关注度的项目才能保持长青。

公司内也是如此,专家级以上的 Title 会要求协作能力,可以调动身边甚至其他部门资源的人才能在公司发挥更大的价值。

CEO 通过顶层设计调动了全公司资源,而业务线总裁通过任务拆解调动了整个业务线的人,通过层层目标拆解,并保证每一层都能充分调动下一层所有资源,公司才能高效的运转。

如果一直关心技术细节,你永远是一个孤立节点,在任何维度的组织中都是最底层,就算 24 小时不睡觉,也最多算两个人力资源。想要突破一天 24 小时的限制,就要花时间让别人认同你的设计,并朝着一个方向努力,你的节点才能上移,但随之而来的是承担更多风险,比如分配给别人的任务给弄砸了,为公司带来了不良影响,那么负责人就要背锅。

3. 总结

总结一下,本文的观点是:

  1. 技术细节学习难度不大,在需要深入的时候再深入了解最佳。
  2. 想要做成事,需要更宏观的技术思维,所以专家渐渐变得眼光宽阔,格局很大。
  3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,比如写方法论的思考带来的价值更大。
  4. 指引方向比走路更重要,专家都要逐渐成为引路人。
  5. 技术最终为业务服务,懂技术细节和让业务先赢没有必然的关系,所以在深入技术细节之前,要先理解业务,把握方向,防止技术细节出现路线问题。
讨论地址是:精读《为什么专家不再关心技术细节》 · Issue #153 · dt-fe/weekly

如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 data-original="https://img.alicdn.com/tfs/TB...;>

special Sponsors

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证
查看原文

bigmeow 赞了文章 · 2019-05-29

精读《为什么专家不再关心技术细节》

1. 引言

本周的精读是有感而发。

笔者接触前端已有八年,观察了不少前端大牛的发展路径,发现成功的人都具有相似的经历:

初期技术热情极大 -> 大量标志性技术项目 -> 转向综合性思考 -> 带团队/关注方法论

也就是专家们变得越来越不关心技术细节。需要说明是的,这里说的专家不再关心细节,不代表成为专家后学不会细节,也不代表专家不了解细节。

早期挺难理解这种转变的,笔者在学校里的知名度来自于前端做得精深,一根筋钻研技术的人眼里是容不下沙子的,所以当初为一些前辈转到管理特别不理解,认为他们背叛了前端。

不过笔者的观念也在逐渐发生转变,渐渐自己也在朝着当初反感的方向发展,觉得这一定不是偶然,所以就整理了一下感悟,希望可以证明这个发展路径的必然性。

2. 精读

首先我们要明确技术员与科学家的区别,为业务提供技术支持都是技术员,所以前端是一门技术,不是科学。

另外,技术的发展需要商业推动,没有使用场景的国家是很难推动技术进步的,科学除外。

所以业务技术是具备可持续发展的路线,毕竟大家都要吃饭,有业务价值的项目会活下来,附着在业务上的技术才能活下来,才有可能开枝散叶。

本文将从三个点去解释,为什么专家看上去越来越原理技术细节。

2.1 技术细节对个人的重要性是在变化的

随着工作年限增加,技术细节重要性在慢慢降低,反之技术视野重要性在慢慢增加。

在找工作初期,技术细节是重要的敲门砖

大学毕业的那段时间,技术细节是一块重要的敲门砖,只有掌握好技术,才会有公司愿意要你。

这也是为什么说毕业生不要一进公司就谈战略,因为时机不对。

技术不是科学,普通人下功夫可以学会

学习技术不需要很聪明的头脑,只要肯下功夫,拥有不错的理解能力,任何人都可以把技术细节搞清楚。

也就是学习技术细节是没有技术门槛,随着年龄的增加,如果只累积了大家都能学会的内容,那么当旧知识被淘汰后,学习新知识的速度又不如年轻人快,会逐渐失去经验优势。

那么如何利用无门槛的特征,将其变为门槛呢?那就是任何年龄段学习技术细节都很容易,在你需要深入细节的时候再深入进去,不需要深入的时候把时间花在了解宏观架构上。

就是培养高效的学习能力,能准确判断某个技术细节是否有必要掌握,如需要该如何快速掌握核心内容,并在掌握之后不留恋,可以快速抽身出来继续全局性思考。这种思维是有门槛的,技术专家都可以做到这一点。

做成事不一定要搞懂细节

乍一看有点匪夷所思:不了解细节怎么能做成事?

虽然理解技术细节可以做成事,但做成事不一定需要理解业务细节。

这要看怎么理解业务与技术的关系,比如建设 “数据联邦”,光是了解各个不同的存储系统技术细节可能就要花很久,而实际上是没必要将所有技术细节都弄懂的,只要定好一个通用交互规范,各存储系统各自封装一套符合这个规范的交互接口即可。

做成事往往需要宏观的技术思维,需要将许多技术点链接在一起。举个例子,做成事就类似于军官指挥作战,做成的目的是通过制定打法赢得战争,而不是自己冲锋陷阵并测量敌人壕沟的宽度。关心技术细节只最终落实到每个人具体实施项中的一部分,技术细节的目标累加起来才是做成事。

2.2 搞清楚业务对技术的真实诉求

业务期望通过技术实现功能,所以技术专家要做的是如何更好的实现业务需求,这就意味着理解业务需求是第一重要的能力。试想一个不能理解业务要做什么的人,即便懂得再多技术细节,对业务也是没有价值的。

业务思维是解决问题,技术思维是创造问题

拥有技术思维的人,容易沉迷于解决不切实际的问题,或者是别人解决过的问题。这种思维对技术学习是非常有帮助的,但如果长期不能转变这种思维,对公司来说是无法创造什么价值的。

拥有业务思维的人,首先要懂业务,只有懂业务,跟着对的业务,才能对未来又信心,知道自己的付出可以换来回报。

懂业务后,才知道如何通过技术帮助业务获得成功。

比如在一家创业公司,老板的眼光很准,进入的时机较早,市场是一片蓝海。你通过分析后,发现要帮助业务占领市场,只要利用某个成熟技术框架快速迭代,就可以在短期帮助业务赢得市场。但是这个框架定制能力不强,如果新需求来了可能需要花时间重构掉。此时技术思维的人只会考虑代码维护性,提出自研一套框架,而拥有业务思维的技术专家会决定先用成熟的技术快速作出原型,等业务稳定后再重构掉。

当然现在互联网市场竞争很激烈,低技术门槛的蓝海基本已都变成了红海,上面提到的场景可能比较少见,我们更多需要决策的是未来几年内业务的收益是否值得现在投入的研发资源。

两个会写框架的人,不如一个能决策的人

另一个简单的例子就是,假如技术专家只会一头扎在技术细节里,对各种前端框架的实现了如指掌,大家都能造出优雅、易用、可维护,而且还带有各自 “特色优势” 的框架或者轮子,那么团队很容易陷入两个专家屁股决定脑袋的技术纷争中。这种情况下,两名技术专家的产出甚至不如一个实习生大,毕竟实习生直接拿来开源框架上手,99% 的情况可靠性比前端专家自己造的轮子更好。

从另一个方面来说,现阶段前端界能写出 React、Vue 框架的人太多了,已经写出来的类 React、Vue 的框架也数不过来。去掉为了练手而做的项目,真正希望推广出去给别人用的还占绝大多数,这是开源界典型的问题:重复低水平造轮子不需要理由,推广给你用也不需要负责任。由于框架属于互联网虚拟资产,边界成本为零,这决定了框架市场一定是个大寡头市场,不可能有类似的项目通过一些不痛不痒的特色分一杯羹。那么就算招 10 个会写框架的人进入公司架构组,最后只有两种可能:要么架构臃肿,每个人都把自己的一部分功劳加入进去;要么就是选择一个更不好的方案,这样不会损害任何一位架构师的利益。

所以现在公司更倾向于内部培养人才,因为内部的人了解业务需要什么,创造的价值往往比空降的架构师更大。

宽广的技术视野更容易借力

现在技术点越来越多,如果什么技术细节都要详细了解,最终一定不能有很好的全局视野。比较好的状态是找几个重点深入了解,其他的技术点在掌握了全局技术视野后再考虑深入。

在互联网初期,很多技术框架还不完善时,技术借力的意义不大,毕竟也没有多少东西可用。

但是现在无论前端还是后端的技术、轮子已经眼花缭乱了,能掌握这些已有技术的人,价值已经逐渐大于会完整了解某些技术细节的人。一个优秀的专家应该能快速定位要解决的业务问题是否有成熟的技术方案,如何以最小的投入产出比实现,同时保持良好的维护性应变业务维护。

2.3 仅仅技术好是无法成为专家的

技术专家真的代表技术壁垒很强的人吗?是的,但只有技术能力是不够的。

为什么开源项目后期要寻找协作者?

我做开源项目的初期,所有框架和源码都事必躬亲,觉得自己有更好的点子可以胜过其他框架。初期很少有贡献者参与,当然我也不愿意其他贡献者参与,毕竟他们不了解设计理念,只有我自己的修改可以让我满意。

还有谁比作者更了解他的开源项目呢?那为什么一个大型开源项目运作到后期,基本都是协作者在维护?

因为开源是一件系统化的事情,如果你想长期维护他,必须建立好文档系统,让你的思路可复制,让他人可参与。如果开源项目只有你一个人懂,那么同时维护两个、四个、六个的时候,你定会发现力不从心。

至于一些开源大神一人维护几百甚至上千 Repo,背后一定有更多的贡献者支持,一个人就算辞职在家专职做开源,也很难同时维护超过 10 个开源项目。你需要拥有开放的心态让更多人加入进来,将成就感和荣誉感分一些给贡献者,他们才会持续为项目贡献。

能够调用资源才能成为专家

开源界就是项目抢占关注度的游戏。假设开源社区总人数为 100,你的项目能够吸引到 10 个人浏览,5 个人使用,2 个人贡献,基本就能存活下来。而开源社区至少有 100 个项目,社区总人数不足以支持每一个项目,只有获得足够关注度的项目才能保持长青。

公司内也是如此,专家级以上的 Title 会要求协作能力,可以调动身边甚至其他部门资源的人才能在公司发挥更大的价值。

CEO 通过顶层设计调动了全公司资源,而业务线总裁通过任务拆解调动了整个业务线的人,通过层层目标拆解,并保证每一层都能充分调动下一层所有资源,公司才能高效的运转。

如果一直关心技术细节,你永远是一个孤立节点,在任何维度的组织中都是最底层,就算 24 小时不睡觉,也最多算两个人力资源。想要突破一天 24 小时的限制,就要花时间让别人认同你的设计,并朝着一个方向努力,你的节点才能上移,但随之而来的是承担更多风险,比如分配给别人的任务给弄砸了,为公司带来了不良影响,那么负责人就要背锅。

3. 总结

总结一下,本文的观点是:

  1. 技术细节学习难度不大,在需要深入的时候再深入了解最佳。
  2. 想要做成事,需要更宏观的技术思维,所以专家渐渐变得眼光宽阔,格局很大。
  3. 专家拥有快速学习技术细节的能力,只是这已不是其核心竞争力,所以与其写技术细节的文章,比如写方法论的思考带来的价值更大。
  4. 指引方向比走路更重要,专家都要逐渐成为引路人。
  5. 技术最终为业务服务,懂技术细节和让业务先赢没有必然的关系,所以在深入技术细节之前,要先理解业务,把握方向,防止技术细节出现路线问题。
讨论地址是:精读《为什么专家不再关心技术细节》 · Issue #153 · dt-fe/weekly

如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

<img width=200 data-original="https://img.alicdn.com/tfs/TB...;>

special Sponsors

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证
查看原文

赞 22 收藏 12 评论 1

bigmeow 评论了文章 · 2019-04-08

一步步编写avalon组件03:切换卡组件

本章开始介绍slot机制。

slot是WEBComponent引进的东西,叫做插槽。在浏览器中,它为一个content元素。不过有资料表明,它会更名为slot。 并且在其他语言的模板引擎中,slot标签更为常用。因此avalon2的组件机制使用slot元素。

但说了这么久,slot元素到底是什么鬼?slot是一个占位符。它有一个name属性。假如另一个地方,也有一个元素,它带一个slot属性,当这两个属性值一致,那么那个元素就会 挪过来,替换掉这个slot元素。

<p id="eee"><slot name="aaa">这是插槽元素,这里面的内容是什么也无关</slot></p>
<p id="kkk"><span slot="aaa">这是要移动的元素</span></p>

最后会转换为

<p id="eee"><span slot="aaa">这是要移动的元素</span></p>
<p id="kkk"></p>

是不是很神奇呢?但是这不是所有浏览器都支持。avalon2 使用了一些魔术让IE6也支持slot。

为了方便我们下面的讲解。我们需要引入更多概念。

slot标签元素: 插槽元素, 用来占位,就是我们上学时,用一本书放在某个座位上占着位置。
带slot属性的元素: 插卡元素,用来替换同名的插槽元素。

然后到我们的组件,我们使用wbr, xmp, template, ms-*等元素来声明它们是某种组件,它们称之为组件容器(所有带ms-widget属性的元素都是插槽元素)。它们与插槽元素一样,是用来占位与被替换掉的。

<div ms-controller='widget1' >
    <xmp :widget="{is:'ms-button'}" ><span slot="content">button!</span></xmp>
    <p><button :click="@click">click</button></p>
</div>

那么组件容器是被谁替换呢?当然是组件。我们使用avalon.component来定义组件时,必须有一个template属性,它是一个HTML模块,它会转换为组件。比如说上面的ms-button.

 

 avalon.component('ms-button', {
    template: '<button type="button"><slot name="content" /></button>',
    defaults: { }
})

template里面有slot元素来占位,而组件容器里面有带slot属性的元素来替换。

clipboard.png

一个组件可以拥有N个slot元素,它们的name值不能重复。但是外面的插卡元素则可以重复。

    avalon.component('ms-tabs', {
        template: '<div><p>它有{{@num}}个面板</p><slot name="tab"/></div>',
        defaults: {
            num: 3
        }
    })
    vm = avalon.define({
        $id: 'widget1'
    })
<div ms-controller='widget1' >
    <xmp :widget="{is:'ms-tabs'}">
        <div slot="tab">面板1</div>
        <hr>
        <div slot="tab">面板2</div>
        <hr>
        <div slot="tab">面板3</div>
        <hr>
    </xmp>
</div>

生成的结构如下:

clipboard.png

这也是我们做切换卡的基础。好了,我们看一下切换卡是如何做的。

DOCTYPE html>

<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script data-original='../../dist/avalon.js'></script>
        <script>
            function heredoc(fn) {
                return fn.toString().replace(/^[^\/]+\/\*!?\s?/, '').
                        replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><')
            }
            avalon.component('ms-tabs', {
                template: heredoc(function () {
                    /*
                     <div>
                     <div><slot name="btn"/></div>
                     <div><slot name="tab"/></div>
                     </div>
                     */
                }),
                defaults: {
                    buttons: [],
                    tabs: [],
                    active: function (index) {
                        this.activeIndex = index
                    },
                    activeIndex: 0,
                }

            })
            var vm = avalon.define({
                $id: 'widget1',
                buttons: [111, 222, 333],
                aa: '动态内容',
                ddd: function () {
                    console.log('xxxx')
                },
                change: function () {
                    vm.aa = '更新内容'+(new Date -0)
                }
            })
        </script>
    </head>
    <body>
        <h1>slot的使用</h1>
        <p>对slot元素使用循环绑定生成大量元素,一起迁进组件内部</p>
        <div ms-controller='widget1' >
            <xmp :widget="{is:'ms-tabs',buttons: @buttons}">
                <button ms-for='(index,button) in @buttons'
                    ms-click='@active(index)'
                    type='button'
                    slot='btn'
                    >{{button}}</button>
                <div slot="tab" ms-visible="0 == @activeIndex">
                    <p>这是面板1</p>
                        
                </div>
                 <div slot="tab" ms-visible="1 == @activeIndex">
                    {{@aa}} <button ms-click="@change" type="button">change</button>

                </div>
                 <div slot="tab" ms-visible="2 == @activeIndex">
                    这是面板3
                </div>
            </xmp>
        </div>
    </body>
</html>

切换卡包含两大块内容,上面用来切换的按钮,及下面的用来显示的面板。由于每次只显示一个面板,我们需要使用ms-visible来做隐藏。

clipboard.png

最后生成的切换卡是这样的。

我们可以下这样的结论。slot用来为组件传入大片内容的, ms-widget配置项是用来传入够短的配置项。

大家可以在这里看到源码

查看原文

bigmeow 收藏了文章 · 2019-01-14

使用 TypeScript 编写一个完善包含测试、文档和持续集成的库

这篇文章主要是讲述如何使用 TypeScript 编写一个完善,包含测试、文档、持续集成的库,涵盖了编写整个库所需要的技术和工具,主要涵盖:

  1. 项目目录骨架
  2. TypeScript 配置
  3. 使用 jest 单元测试
  4. 使用 vuepress 编写文档
  5. 使用 github pages 部署文档
  6. 持续集成部署

原文首发于我的个人网站:听说 - https://tasaid.com/,推荐在我的网站阅读更多技术文章。

前端开发 QQ 群:377786580
欢迎使用和了解滴滴金融出品的移动端组件库 Mand-mobile

为了迎合这篇文章,我编写了一个可以开箱即用的库模板:https://github.com/linkFly6/ts-lib-basic

里面集成了这篇文章所阐述的所有内容。

初始化项目目录

先初始化项目目录,一般来说,src 放源码,dist 放编译后的代码,tests 放单元测试,所以先初始化好基础目录。

.
├── .vscode           # vscode 配置
│   └── launch.json   # vscode 调试配置
├── dist              # 编译产出目录,编译后才有
├── src               # 源码
├── tests             # 单元测试
├── .gitignore        # git 忽略文件
├── .npmrc            # npm 配置
├── .travis.yml       # github 持续集成
├── LICENSE           # 开源协议
├── README.md         # README
├── package-lock.json # npm 锁定依赖
├── package.json      # npm
├── tsconfig.json     # typescript 配置
└── tslint.json       # tslint 校验

先按照这个目录文件结构,然后我们会一步步填上内容。

通过 npm init 初始化一个 npm 配置:

clipboard.png

初始化 TypeScript 相关工具

既然包是基于 TypeScript 的,那么 TypeScript 工具必不可少。

ts-node

在开发中,可以使用 ts-node(可以理解为可以直接执行 ts 文件的 node)来直接运行我们的 ts 代码。

npm i --save-dev typescript
npm i --save-dev ts-node

如果是 node 应用,为了让 TypeScript 能够进行 node 类型推导,则需要安装 Node 对应的类型声明:

npm i --save-dev @types/node

tsconfig.json

tsconfig.json 是 TypeScript 的配置文件,这里提供一份可供参考是配置,置于项目根目录:

{
  "compilerOptions": {
    "sourceMap": false,
    "module": "commonjs", // 模块配置
    "noImplicitAny": true, // 是否默认禁用 any
    // "removeComments": true, // 是否移除注释
    "types": [ // 默认引入的类型声明
      "node", // 默认引入 node 的类型声明
    ],
    "baseUrl": ".", // 工作根目录
    "paths": {
      // ~/ 指向 server/types,types 目录下都是 types 文件,所以不会编译产出
      "~/*": [
        "./types/*"
      ]
    },
    "target": "es6", // 编译目标
    "outDir": "dist", // 输出目录
    "declaration": true, // 是否自动创建类型声明
  },
  // 此配置生效范围
  "include": [
    "src/**/*"
  ],
}

tslint.json

tslint 类似 eslint,是 TypeScript 中的代码风格约束工具。

关于 lint,个人方面比较倾向于非强制性的,所以只在 vscode 中安装了扩展 tslint,这样 vscode 会根据项目根目录配置的 tslint.json 标出不符合规范的信息。

这里有一份推荐配置:

{
  "defaultSeverity": "error",
  "extends": [
    "tslint:recommended"
  ],
  "jsRules": {},
  "rules": {
    "max-line-length": [
      true,
      140
    ],
    // 禁止内置原始类型
    "ban-types": false,
    // 禁止给参数赋值
    "no-parameter-reassignment": false,
    // 禁止空接口
    "no-empty-interface": true,
    // 显示类型代码就不需要再加类型声明了
    "no-inferrable-types": true,
    // 不允许使用内部模块
    "no-internal-module": true,
    // 不允许在变量赋值之外使用常量数值。如果未指定允许值的列表, 则默认情况下允许-1、0和1 => 乱七八糟的数字会让人混淆
    // "no-magic-numbers": [true],
    // 不允许使用内部 'modules' 和 'namespace'
    "no-namespace": true,
    // 非空断言,强制使用 == null 之类的断言
    // "no-non-null-assertion": true
    // 禁止 /// <reference path=>,直接用 import 即可
    "no-reference": true,
    // 禁止使用 require,应该使用 import foo = require('foo')
    "no-var-requires": false,
    // import 的顺序按照字母表
    "ordered-imports": false,
    // 对象属性声明按照字母表
    "object-literal-sort-keys": false,
    // // 结束语句后的分号
    "semicolon": [
      false,
      "always"
    ],
    // 字符串强制单引号
    "quotemark": [
      true,
      "single",
      "jsx-double"
    ],
    // 禁止 arguments.callee
    "no-arg": true,
    // if 语句的单行不用括号,多行用括号
    "curly": false,
    // 是否强制使用箭头函数,禁止匿名函数
    "only-arrow-functions": false,
    // 是否禁止多个空行
    "no-consecutive-blank-lines": false,
    // 在函数括号前要求或不允许空格
    "space-before-function-paren": false,
    // 箭头函数的参数使用括号
    "arrow-parens": [
      true,
      "ban-single-arg-parens"
    ],
    // 不固定变量类型
    "no-shadowed-variable": false,
    // 行尾多余的空格
    "no-trailing-whitespace": false,
    // == 和 ===
    "triple-equals": false,
    // 禁止一些位运算符
    "no-bitwise": false,
    // 禁止 console
    "no-console": false,
    // 检查变量名
    "variable-name": [
      true,
      "ban-keywords"
      // "check-format",
      // "allow-leading-underscore"
    ],
    // 一行声明变量表达式
    "one-variable-per-declaration": false,
    // 允许在一个文件里定义多个 class
    "max-classes-per-file": [
      true,
      5
    ],
    // 判断表达式 fn && fn()
    "no-unused-expression": [
      true,
      "allow-fast-null-checks"
    ],
    // 空函数
    "no-empty": false,
    // forin 是否必须包含 hasOwnProperty 判断
    "forin": false,
    "no-debugger": false,
    // 强制要求必须要声明类型
    "typedef": [
      true
    ]
  },
  "rulesDirectory": [
    "./src"
  ]
}

package-lock.json

package-lock.json 是 npm 5 之后引入的,为了解决 npm 过去使用的 package.json 版本依赖太宽松的问题。

比如说 package.json 中依赖了包 mand-mobile,使用了最常用的插入依赖(^):

"mand-mobile": "^4.16.4",

假设自己项目在上线阶段, mand-mobile 更新到了 mand-mobile@4.17.0,而刚好 mand-mobile@4.17.0 又不小心出现了一个新 bug 会导致页面脚本错误。这时候上线安装依赖的时候,由于 package.json^ 约束太宽松,就会导致 mand-mobile@4.17.0 被安装,从而导致上线出问题。

package-lock.json 就是为了解决这个问题,通过 npm 安装包的时候,会检测本地是否有 package-lock.json

  • 如果没有 package-lock.json,就在安装包的时候将当前包依赖的详细信息(包括子级依赖)都写入生成 package-lock.json
  • 如果有 package-lock.json,则根据 package.json,参考 pacakge-lock.json 来安装包依赖。来保证依赖稳定。

本质上 ppackage-lock.json 的作用类似于 node_modules 包依赖的快照。

单元测试

一个合格的库应该包含完整的单元测试。这里我们使用 jest 对应的 TypeScript 版本:ts-jest

ts-jest

ts-jestjest 的 TypeScript 支持版,API 和 jest 是一样的,它能够直接运行 .ts 为后缀的单元测试文件。

安装 ts-jest 和对应的类型声明文件:

npm i --save-dev jest  #ts-jest 依赖 jest
npm i --save-dev ts-jest
npm i --save-dev @types/jest

package.json 中加入 jest 配置和 npm run test 的脚本:

{
  "name": "my-app",
  "main": "dist/index.js",
  "scripts": {
    "test": "jest --verbose"
  },
  "jest": {
    "rootDir": "tests",
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

这时候就可以基于 jest 编写单元测试了。在 tests/ 目录下加入 example.test.ts

import { isArrayLike } from '../src'

describe('my-app:isArrayLike', () => {
  test('isArrayLike(): true', () => {
    expect(
      isArrayLike([]),
    ).toBe(true)
  })

  test('isArrayLike(): false', () => {
    expect(
      isArrayLike({}),
    ).toBe(false)
  })
})

然后执行 npm run test 即可看到单元测试结果。

clipboard.png

express 测试

如果要测试 express/koa 之类的 web 应用框架程序,则可以使用 tj 大神的 supertest

安装对应的包:

npm i --save-dev supertest
npm i --save-dev @types/supertest
import * as express from 'express'
/**
 * 用于测试 express、koa 等 web 应用框架的工具
 */
import * as request from 'supertest'
import middleware from '../src'

describe('my-app:basic', () => {
  test('locals', done => {
    const app = express()
    app.use(middleware)
    app.get('/example', (req, res) => {
      res.send({ code: 0 })
    })
    // 使用 supertest 进行测试
    request(app).get('/example').expect(200, { code: 0 }, done)
  })
})

debug

debug 也是 tj 大神编写的一个库,用于在应用程序中输出 debug 信息,用于调试工具库,著名的库大部分都采用该库进行 debug 支持。

npm i --save debug
npm i --save-dev @types/debug
import * as d from 'debug'

const debug = d(`my-app:basic`)

debug('debug info')

在启动应用程序的时候,只需要在环境变量中注入 DEBUG 即可:

DEBUG=my-app* node app.js

DEBUG=my-app* ts-node app.ts

clipboard.png

vscode 基于 ts-node 调试

.vscode/launch.json 中可以配置基于 ts-node 的调试:

{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "启动程序",
      // 基于 ts-node 调试
      "program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
      "args": [
        "-P",
        "${workspaceRoot}/tests/tsconfig.json",
        "${workspaceRoot}/tests/app.ts", // 入口文件
      ]
    }
  ]
}

clipboard.png

文档

文档方面,简陋一点的,可以直接使用 README,也可以用 gitbook。不过我个人方便比较推荐 vuepress

远程托管文档方面,要么自建服务器,要么直接托管到 Github 的 Pages。

使用 vuepress 编写文档

个人比较倾向于使用 vuepress 编写文档,是因为里面扩展 Markdown 扩展了许多丰富实用的语法,以及菜单结构的强大可配置。

这里我们讨论的是在项目中集成文档。

  1. 在项目根目录新建目录 /docs
  2. npm i --save-dev vuepress
  3. 在项目的 package.json 中加入脚本
  "scripts": {
    "docs": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }

/docs 新增文件 README.md,写入以下内容:

---
home: true
actionText: 开始使用 →
actionLink: /readme
footer: MIT Licensed | Copyright © 2018-present linkFly
features:
- title: 快速
  details: 快速创建库
- title: 集成
  details: 集成单元测试和自动化 doc 部署
- title: TypeScript
  details: TypeScript 支持
---

集成了基础工具的,使用 TypeScript 快速编写一个应用库

然后执行结合我们刚才配置的命令,执行 npm run docs,终端 shell 会输出 vuepress 启动的服务地址:

clipboard.png

访问地址,即可看到文档页面:

clipboard.png

使用 github pages 托管文档

github pages 是 Github 提供的一个免费的页面托管服务,我们可以将 vuexpress 编译出来的文档托管到上面。

Github Pages 服务和 Github 已经打通,可以从项目的 /docs 目录自动部署,这也就是我们为什么要在项目里新建 /docs 目录的原因。

首先,我们将项目中 pageage.json 的脚本进行更新:

  "scripts": {
    "docs:build": "vuepress build docs && cp -rf ./docs/.vuepress/dist/* ./docs && rm -r ./docs/.vuepress/dist"
  }

这段脚本的大体意思就是先使用 vuepress 构建产出文档的 HTML 文件(在 /docs/.vuepress/dist 目录下),然后将 dist 目录移动到 docs/ 目录下,因为 Github Pages 在识别 docs/ 的时候只能识别 docs/index.html

执行 npm run docs:build

将本地的项目 push 到 Github 以后,打开该项目的 Setting

clipboard.png

在 Github Pages 配置项选择 docs/ 文件夹:

clipboard.png

然后访问 https://<USERNAME or GROUP>.gitlab.io/<REPO>/ 即可看到自动部署的文档。例如:https://linkfly6.github.io/ts-lib-basic/

使用持续集成服务 travis-ci

travis-ci 是一个持续集成服务,它可以用来自动部署和构建 Github 上的项目。

我们可以集成我们的单元测试。

在项目根目录加入 .travis.yml,在 master 分支进行提交的时候自动运行 npm run test 命令(npm run test 命令配置参见 ts-jest 章节):

sudo: false
language: node_js
node_js:
  - "8"

cache:
  directories:
  - node_modules

branches:
  only:
  - master

script:
  npm run test

打开 https://travis-ci.org/ 进行注册或登录。新增接入的项目:

clipboard.png

选择要打开持续集成的项目:

clipboard.png

然后我们更新文档或代码,提交代码到 Github。

稍等大概几十秒,就可以在 travis-ci 里面看到自己的单元测试任务:

clipboard.png

最后,在测试完毕的情况下,在 https://www.npmjs.com/ 进行注册。

在 npm 的源是官方的(npm config set registry https://registry.npmjs.org/)情况下,执行 npm login 登录 npm 以后,npm publish 发布包即可。

最后,为了迎合这篇文章,我编写了一个可以开箱即用的库模板:https://github.com/linkFly6/ts-lib-basic

里面集成了这篇文章所阐述的所有内容。

前端开发 QQ 群:377786580
欢迎使用和了解金融出品的移动端组件库 Mand-mobile

原文首发于我的个人网站:听说 - https://tasaid.com/,推荐在我的网站阅读更多技术文章。

查看原文

bigmeow 赞了文章 · 2019-01-14

使用 TypeScript 编写一个完善包含测试、文档和持续集成的库

这篇文章主要是讲述如何使用 TypeScript 编写一个完善,包含测试、文档、持续集成的库,涵盖了编写整个库所需要的技术和工具,主要涵盖:

  1. 项目目录骨架
  2. TypeScript 配置
  3. 使用 jest 单元测试
  4. 使用 vuepress 编写文档
  5. 使用 github pages 部署文档
  6. 持续集成部署

原文首发于我的个人网站:听说 - https://tasaid.com/,推荐在我的网站阅读更多技术文章。

前端开发 QQ 群:377786580
欢迎使用和了解滴滴金融出品的移动端组件库 Mand-mobile

为了迎合这篇文章,我编写了一个可以开箱即用的库模板:https://github.com/linkFly6/ts-lib-basic

里面集成了这篇文章所阐述的所有内容。

初始化项目目录

先初始化项目目录,一般来说,src 放源码,dist 放编译后的代码,tests 放单元测试,所以先初始化好基础目录。

.
├── .vscode           # vscode 配置
│   └── launch.json   # vscode 调试配置
├── dist              # 编译产出目录,编译后才有
├── src               # 源码
├── tests             # 单元测试
├── .gitignore        # git 忽略文件
├── .npmrc            # npm 配置
├── .travis.yml       # github 持续集成
├── LICENSE           # 开源协议
├── README.md         # README
├── package-lock.json # npm 锁定依赖
├── package.json      # npm
├── tsconfig.json     # typescript 配置
└── tslint.json       # tslint 校验

先按照这个目录文件结构,然后我们会一步步填上内容。

通过 npm init 初始化一个 npm 配置:

clipboard.png

初始化 TypeScript 相关工具

既然包是基于 TypeScript 的,那么 TypeScript 工具必不可少。

ts-node

在开发中,可以使用 ts-node(可以理解为可以直接执行 ts 文件的 node)来直接运行我们的 ts 代码。

npm i --save-dev typescript
npm i --save-dev ts-node

如果是 node 应用,为了让 TypeScript 能够进行 node 类型推导,则需要安装 Node 对应的类型声明:

npm i --save-dev @types/node

tsconfig.json

tsconfig.json 是 TypeScript 的配置文件,这里提供一份可供参考是配置,置于项目根目录:

{
  "compilerOptions": {
    "sourceMap": false,
    "module": "commonjs", // 模块配置
    "noImplicitAny": true, // 是否默认禁用 any
    // "removeComments": true, // 是否移除注释
    "types": [ // 默认引入的类型声明
      "node", // 默认引入 node 的类型声明
    ],
    "baseUrl": ".", // 工作根目录
    "paths": {
      // ~/ 指向 server/types,types 目录下都是 types 文件,所以不会编译产出
      "~/*": [
        "./types/*"
      ]
    },
    "target": "es6", // 编译目标
    "outDir": "dist", // 输出目录
    "declaration": true, // 是否自动创建类型声明
  },
  // 此配置生效范围
  "include": [
    "src/**/*"
  ],
}

tslint.json

tslint 类似 eslint,是 TypeScript 中的代码风格约束工具。

关于 lint,个人方面比较倾向于非强制性的,所以只在 vscode 中安装了扩展 tslint,这样 vscode 会根据项目根目录配置的 tslint.json 标出不符合规范的信息。

这里有一份推荐配置:

{
  "defaultSeverity": "error",
  "extends": [
    "tslint:recommended"
  ],
  "jsRules": {},
  "rules": {
    "max-line-length": [
      true,
      140
    ],
    // 禁止内置原始类型
    "ban-types": false,
    // 禁止给参数赋值
    "no-parameter-reassignment": false,
    // 禁止空接口
    "no-empty-interface": true,
    // 显示类型代码就不需要再加类型声明了
    "no-inferrable-types": true,
    // 不允许使用内部模块
    "no-internal-module": true,
    // 不允许在变量赋值之外使用常量数值。如果未指定允许值的列表, 则默认情况下允许-1、0和1 => 乱七八糟的数字会让人混淆
    // "no-magic-numbers": [true],
    // 不允许使用内部 'modules' 和 'namespace'
    "no-namespace": true,
    // 非空断言,强制使用 == null 之类的断言
    // "no-non-null-assertion": true
    // 禁止 /// <reference path=>,直接用 import 即可
    "no-reference": true,
    // 禁止使用 require,应该使用 import foo = require('foo')
    "no-var-requires": false,
    // import 的顺序按照字母表
    "ordered-imports": false,
    // 对象属性声明按照字母表
    "object-literal-sort-keys": false,
    // // 结束语句后的分号
    "semicolon": [
      false,
      "always"
    ],
    // 字符串强制单引号
    "quotemark": [
      true,
      "single",
      "jsx-double"
    ],
    // 禁止 arguments.callee
    "no-arg": true,
    // if 语句的单行不用括号,多行用括号
    "curly": false,
    // 是否强制使用箭头函数,禁止匿名函数
    "only-arrow-functions": false,
    // 是否禁止多个空行
    "no-consecutive-blank-lines": false,
    // 在函数括号前要求或不允许空格
    "space-before-function-paren": false,
    // 箭头函数的参数使用括号
    "arrow-parens": [
      true,
      "ban-single-arg-parens"
    ],
    // 不固定变量类型
    "no-shadowed-variable": false,
    // 行尾多余的空格
    "no-trailing-whitespace": false,
    // == 和 ===
    "triple-equals": false,
    // 禁止一些位运算符
    "no-bitwise": false,
    // 禁止 console
    "no-console": false,
    // 检查变量名
    "variable-name": [
      true,
      "ban-keywords"
      // "check-format",
      // "allow-leading-underscore"
    ],
    // 一行声明变量表达式
    "one-variable-per-declaration": false,
    // 允许在一个文件里定义多个 class
    "max-classes-per-file": [
      true,
      5
    ],
    // 判断表达式 fn && fn()
    "no-unused-expression": [
      true,
      "allow-fast-null-checks"
    ],
    // 空函数
    "no-empty": false,
    // forin 是否必须包含 hasOwnProperty 判断
    "forin": false,
    "no-debugger": false,
    // 强制要求必须要声明类型
    "typedef": [
      true
    ]
  },
  "rulesDirectory": [
    "./src"
  ]
}

package-lock.json

package-lock.json 是 npm 5 之后引入的,为了解决 npm 过去使用的 package.json 版本依赖太宽松的问题。

比如说 package.json 中依赖了包 mand-mobile,使用了最常用的插入依赖(^):

"mand-mobile": "^4.16.4",

假设自己项目在上线阶段, mand-mobile 更新到了 mand-mobile@4.17.0,而刚好 mand-mobile@4.17.0 又不小心出现了一个新 bug 会导致页面脚本错误。这时候上线安装依赖的时候,由于 package.json^ 约束太宽松,就会导致 mand-mobile@4.17.0 被安装,从而导致上线出问题。

package-lock.json 就是为了解决这个问题,通过 npm 安装包的时候,会检测本地是否有 package-lock.json

  • 如果没有 package-lock.json,就在安装包的时候将当前包依赖的详细信息(包括子级依赖)都写入生成 package-lock.json
  • 如果有 package-lock.json,则根据 package.json,参考 pacakge-lock.json 来安装包依赖。来保证依赖稳定。

本质上 ppackage-lock.json 的作用类似于 node_modules 包依赖的快照。

单元测试

一个合格的库应该包含完整的单元测试。这里我们使用 jest 对应的 TypeScript 版本:ts-jest

ts-jest

ts-jestjest 的 TypeScript 支持版,API 和 jest 是一样的,它能够直接运行 .ts 为后缀的单元测试文件。

安装 ts-jest 和对应的类型声明文件:

npm i --save-dev jest  #ts-jest 依赖 jest
npm i --save-dev ts-jest
npm i --save-dev @types/jest

package.json 中加入 jest 配置和 npm run test 的脚本:

{
  "name": "my-app",
  "main": "dist/index.js",
  "scripts": {
    "test": "jest --verbose"
  },
  "jest": {
    "rootDir": "tests",
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

这时候就可以基于 jest 编写单元测试了。在 tests/ 目录下加入 example.test.ts

import { isArrayLike } from '../src'

describe('my-app:isArrayLike', () => {
  test('isArrayLike(): true', () => {
    expect(
      isArrayLike([]),
    ).toBe(true)
  })

  test('isArrayLike(): false', () => {
    expect(
      isArrayLike({}),
    ).toBe(false)
  })
})

然后执行 npm run test 即可看到单元测试结果。

clipboard.png

express 测试

如果要测试 express/koa 之类的 web 应用框架程序,则可以使用 tj 大神的 supertest

安装对应的包:

npm i --save-dev supertest
npm i --save-dev @types/supertest
import * as express from 'express'
/**
 * 用于测试 express、koa 等 web 应用框架的工具
 */
import * as request from 'supertest'
import middleware from '../src'

describe('my-app:basic', () => {
  test('locals', done => {
    const app = express()
    app.use(middleware)
    app.get('/example', (req, res) => {
      res.send({ code: 0 })
    })
    // 使用 supertest 进行测试
    request(app).get('/example').expect(200, { code: 0 }, done)
  })
})

debug

debug 也是 tj 大神编写的一个库,用于在应用程序中输出 debug 信息,用于调试工具库,著名的库大部分都采用该库进行 debug 支持。

npm i --save debug
npm i --save-dev @types/debug
import * as d from 'debug'

const debug = d(`my-app:basic`)

debug('debug info')

在启动应用程序的时候,只需要在环境变量中注入 DEBUG 即可:

DEBUG=my-app* node app.js

DEBUG=my-app* ts-node app.ts

clipboard.png

vscode 基于 ts-node 调试

.vscode/launch.json 中可以配置基于 ts-node 的调试:

{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "启动程序",
      // 基于 ts-node 调试
      "program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
      "args": [
        "-P",
        "${workspaceRoot}/tests/tsconfig.json",
        "${workspaceRoot}/tests/app.ts", // 入口文件
      ]
    }
  ]
}

clipboard.png

文档

文档方面,简陋一点的,可以直接使用 README,也可以用 gitbook。不过我个人方便比较推荐 vuepress

远程托管文档方面,要么自建服务器,要么直接托管到 Github 的 Pages。

使用 vuepress 编写文档

个人比较倾向于使用 vuepress 编写文档,是因为里面扩展 Markdown 扩展了许多丰富实用的语法,以及菜单结构的强大可配置。

这里我们讨论的是在项目中集成文档。

  1. 在项目根目录新建目录 /docs
  2. npm i --save-dev vuepress
  3. 在项目的 package.json 中加入脚本
  "scripts": {
    "docs": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }

/docs 新增文件 README.md,写入以下内容:

---
home: true
actionText: 开始使用 →
actionLink: /readme
footer: MIT Licensed | Copyright © 2018-present linkFly
features:
- title: 快速
  details: 快速创建库
- title: 集成
  details: 集成单元测试和自动化 doc 部署
- title: TypeScript
  details: TypeScript 支持
---

集成了基础工具的,使用 TypeScript 快速编写一个应用库

然后执行结合我们刚才配置的命令,执行 npm run docs,终端 shell 会输出 vuepress 启动的服务地址:

clipboard.png

访问地址,即可看到文档页面:

clipboard.png

使用 github pages 托管文档

github pages 是 Github 提供的一个免费的页面托管服务,我们可以将 vuexpress 编译出来的文档托管到上面。

Github Pages 服务和 Github 已经打通,可以从项目的 /docs 目录自动部署,这也就是我们为什么要在项目里新建 /docs 目录的原因。

首先,我们将项目中 pageage.json 的脚本进行更新:

  "scripts": {
    "docs:build": "vuepress build docs && cp -rf ./docs/.vuepress/dist/* ./docs && rm -r ./docs/.vuepress/dist"
  }

这段脚本的大体意思就是先使用 vuepress 构建产出文档的 HTML 文件(在 /docs/.vuepress/dist 目录下),然后将 dist 目录移动到 docs/ 目录下,因为 Github Pages 在识别 docs/ 的时候只能识别 docs/index.html

执行 npm run docs:build

将本地的项目 push 到 Github 以后,打开该项目的 Setting

clipboard.png

在 Github Pages 配置项选择 docs/ 文件夹:

clipboard.png

然后访问 https://<USERNAME or GROUP>.gitlab.io/<REPO>/ 即可看到自动部署的文档。例如:https://linkfly6.github.io/ts-lib-basic/

使用持续集成服务 travis-ci

travis-ci 是一个持续集成服务,它可以用来自动部署和构建 Github 上的项目。

我们可以集成我们的单元测试。

在项目根目录加入 .travis.yml,在 master 分支进行提交的时候自动运行 npm run test 命令(npm run test 命令配置参见 ts-jest 章节):

sudo: false
language: node_js
node_js:
  - "8"

cache:
  directories:
  - node_modules

branches:
  only:
  - master

script:
  npm run test

打开 https://travis-ci.org/ 进行注册或登录。新增接入的项目:

clipboard.png

选择要打开持续集成的项目:

clipboard.png

然后我们更新文档或代码,提交代码到 Github。

稍等大概几十秒,就可以在 travis-ci 里面看到自己的单元测试任务:

clipboard.png

最后,在测试完毕的情况下,在 https://www.npmjs.com/ 进行注册。

在 npm 的源是官方的(npm config set registry https://registry.npmjs.org/)情况下,执行 npm login 登录 npm 以后,npm publish 发布包即可。

最后,为了迎合这篇文章,我编写了一个可以开箱即用的库模板:https://github.com/linkFly6/ts-lib-basic

里面集成了这篇文章所阐述的所有内容。

前端开发 QQ 群:377786580
欢迎使用和了解金融出品的移动端组件库 Mand-mobile

原文首发于我的个人网站:听说 - https://tasaid.com/,推荐在我的网站阅读更多技术文章。

查看原文

赞 22 收藏 16 评论 0

bigmeow 收藏了文章 · 2018-12-19

babel各单元简介&如何写一个babel插件

Babel

babel是怎么工作的?

parse->AST->transform->gengerate

如何编译js->AST

babel应用场景

语法糖的polyfill

代码统一hack

相关概念介绍

babel-polyfill

依赖core-js,提供es*->es3的方法,只转化语法,不转换API(类Promise,WeakMap)

babel-helper

babel-register(0.7.0-beta)

babel的基础配置init

利用pirate对require进行劫持,在hook中进行babel 原理见

同时对babel后的code进行缓存,提高下次babel效率


function compile(code, filename) {
    ...

  let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;

  const env = babel.getEnv(false);

  if (env) cacheKey += `:${env}`;

    //读取缓存 根据mtime判断是否需要重新babel
  if (cache) {
    const cached = cache[cacheKey];
    if (cached && cached.mtime === mtime(filename)) {
      return cached.code;
    }
  }

  const result = babel.transform(code, {
    ...opts,
    sourceMaps: opts.sourceMaps === undefined ? "both" : opts.sourceMaps,
    ast: false,
  });

  if (cache) {
    cache[cacheKey] = result;
    result.mtime = mtime(filename);
  }

  if (result.map) {
    if (Object.keys(maps).length === 0) {
      installSourceMapSupport();
    }
    maps[filename] = result.map;
  }

  return result.code;
}

//hook中传入ext配置
function hookExtensions(exts) {
  if (piratesRevert) piratesRevert();
  piratesRevert = addHook(compile, { exts, ignoreNodeModules: false });
}

//入口函数
export default function register(opts?: Object = {}) {
  // Clone to avoid mutating the arguments object with the 'delete's below.
  opts = Object.assign({}, opts);
  if (opts.extensions) hookExtensions(opts.extensions);

  if (opts.cache === false && cache) {
    registerCache.clear();
    cache = null;
  } else if (opts.cache !== false && !cache) {
    registerCache.load();
    cache = registerCache.get();
  }
  
  ...
}

babel-core

提供基础的transform方法

如何写一个babel插件

babel-plugin其实是对code转出的ast进行操作,

准备工具

ast转换工具

ast转换可视化工具

ast的解构可以类比成一个树状或者json嵌套结构,他的每一层结构都可以叫做一个节点,如下图

babel提供一个visitor的方法,允许我们在里面指定我们想要访问的节点,并且可以在命中该节点时做出自定义的的操作

实例分析

现在我们有一个需要移除整个业务bundle包里所有console.log的需求

1.那我们首先要知道console.log实际在ast是怎样的一个节点结构

形如

console.log('a')

实际ast的展现如下

对于各个节点具体含义,这里不做细讲,可以参考文末的babel手册

2.这里直接贴上代码讲吧


module.exports = function (babel) {

    const { types: t, template } = babel;

    const visitor = {
            //需要访问的节点名
            //访问器默认会被注入两个参数 path(类比成dom),state
        ExpressionStatement(path, state) {
            const node = path.node;
            //延当前节点向内部访问,判断是否符合console解析出的ast的特征
            const expressionNode = keyPathVisitor(node, ['expression']);
            const isCallExpression = expressionNode.type === 'CallExpression';
            if (isCallExpression) {
                const objectName = keyPathVisitor(expressionNode, ['callee', 'object', 'name']);
                const prototypeName = keyPathVisitor(expressionNode, ['callee', 'property', 'name']);
                if (objectName === 'console' && prototypeName === 'log' && !MAC) {
                        //如果符合上述条件,直接移除该节点
                    path.remove();
                }
            }
        }
    };

    return {
        visitor
    };
};

3.进阶版:如果我们想在babel-plugin中新增代码呢

差不多有三种方法

A:手动添加节点(很恶心~相信你不会想去了解)
B:先生成ast,直接path.insertBefore
C:使用babel-template
例子: 移除autobind装饰器,并在constructor中自动bind this

注意点

1.因为babel判断是否babel是根据modify time,所以babel插件写完想实时生效,需要给当前的env加上 BABEL_DISABLE_CACHE


//babel-register/cache.js

function load() {
  if (process.env.BABEL_DISABLE_CACHE) return;
    
  process.on("exit", save);
  process.nextTick(save);
    
  if (!_fs2.default.existsSync(FILENAME)) return;
    
  try {
    data = JSON.parse(_fs2.default.readFileSync(FILENAME));
  } catch (err) {
    return;
  }
}

2.babel插件写完后发布npm时,记得一定要加上babel-plugin-前缀,因为配置在babelrc中的插件名都会被babel在加载时统一加上babel-plugin前缀,然后在模块系统中去查找

题外话

如何实现给require加上hook

传送门

参考文献

Babel插件手册

查看原文

bigmeow 赞了文章 · 2018-12-19

babel各单元简介&如何写一个babel插件

Babel

babel是怎么工作的?

parse->AST->transform->gengerate

如何编译js->AST

babel应用场景

语法糖的polyfill

代码统一hack

相关概念介绍

babel-polyfill

依赖core-js,提供es*->es3的方法,只转化语法,不转换API(类Promise,WeakMap)

babel-helper

babel-register(0.7.0-beta)

babel的基础配置init

利用pirate对require进行劫持,在hook中进行babel 原理见

同时对babel后的code进行缓存,提高下次babel效率


function compile(code, filename) {
    ...

  let cacheKey = `${JSON.stringify(opts)}:${babel.version}`;

  const env = babel.getEnv(false);

  if (env) cacheKey += `:${env}`;

    //读取缓存 根据mtime判断是否需要重新babel
  if (cache) {
    const cached = cache[cacheKey];
    if (cached && cached.mtime === mtime(filename)) {
      return cached.code;
    }
  }

  const result = babel.transform(code, {
    ...opts,
    sourceMaps: opts.sourceMaps === undefined ? "both" : opts.sourceMaps,
    ast: false,
  });

  if (cache) {
    cache[cacheKey] = result;
    result.mtime = mtime(filename);
  }

  if (result.map) {
    if (Object.keys(maps).length === 0) {
      installSourceMapSupport();
    }
    maps[filename] = result.map;
  }

  return result.code;
}

//hook中传入ext配置
function hookExtensions(exts) {
  if (piratesRevert) piratesRevert();
  piratesRevert = addHook(compile, { exts, ignoreNodeModules: false });
}

//入口函数
export default function register(opts?: Object = {}) {
  // Clone to avoid mutating the arguments object with the 'delete's below.
  opts = Object.assign({}, opts);
  if (opts.extensions) hookExtensions(opts.extensions);

  if (opts.cache === false && cache) {
    registerCache.clear();
    cache = null;
  } else if (opts.cache !== false && !cache) {
    registerCache.load();
    cache = registerCache.get();
  }
  
  ...
}

babel-core

提供基础的transform方法

如何写一个babel插件

babel-plugin其实是对code转出的ast进行操作,

准备工具

ast转换工具

ast转换可视化工具

ast的解构可以类比成一个树状或者json嵌套结构,他的每一层结构都可以叫做一个节点,如下图

babel提供一个visitor的方法,允许我们在里面指定我们想要访问的节点,并且可以在命中该节点时做出自定义的的操作

实例分析

现在我们有一个需要移除整个业务bundle包里所有console.log的需求

1.那我们首先要知道console.log实际在ast是怎样的一个节点结构

形如

console.log('a')

实际ast的展现如下

对于各个节点具体含义,这里不做细讲,可以参考文末的babel手册

2.这里直接贴上代码讲吧


module.exports = function (babel) {

    const { types: t, template } = babel;

    const visitor = {
            //需要访问的节点名
            //访问器默认会被注入两个参数 path(类比成dom),state
        ExpressionStatement(path, state) {
            const node = path.node;
            //延当前节点向内部访问,判断是否符合console解析出的ast的特征
            const expressionNode = keyPathVisitor(node, ['expression']);
            const isCallExpression = expressionNode.type === 'CallExpression';
            if (isCallExpression) {
                const objectName = keyPathVisitor(expressionNode, ['callee', 'object', 'name']);
                const prototypeName = keyPathVisitor(expressionNode, ['callee', 'property', 'name']);
                if (objectName === 'console' && prototypeName === 'log' && !MAC) {
                        //如果符合上述条件,直接移除该节点
                    path.remove();
                }
            }
        }
    };

    return {
        visitor
    };
};

3.进阶版:如果我们想在babel-plugin中新增代码呢

差不多有三种方法

A:手动添加节点(很恶心~相信你不会想去了解)
B:先生成ast,直接path.insertBefore
C:使用babel-template
例子: 移除autobind装饰器,并在constructor中自动bind this

注意点

1.因为babel判断是否babel是根据modify time,所以babel插件写完想实时生效,需要给当前的env加上 BABEL_DISABLE_CACHE


//babel-register/cache.js

function load() {
  if (process.env.BABEL_DISABLE_CACHE) return;
    
  process.on("exit", save);
  process.nextTick(save);
    
  if (!_fs2.default.existsSync(FILENAME)) return;
    
  try {
    data = JSON.parse(_fs2.default.readFileSync(FILENAME));
  } catch (err) {
    return;
  }
}

2.babel插件写完后发布npm时,记得一定要加上babel-plugin-前缀,因为配置在babelrc中的插件名都会被babel在加载时统一加上babel-plugin前缀,然后在模块系统中去查找

题外话

如何实现给require加上hook

传送门

参考文献

Babel插件手册

查看原文

赞 4 收藏 2 评论 1

认证与成就

  • 获得 38 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-04-23
个人主页被 267 人浏览