以我的小经验来看,软件萌新写出来的代码大多“无法直视”。具体现象包括空格和换行符乱用、文件夹和变量的命名多使用拼音等。坐不住的我,便想到了通过 ESLint 配置文件来规范实验室的 JavaScript 代码规范的 Idea。

于是巧遇前实验室毕业学长曾经发布的 npm 包——creatshare-project-quick-init。安装好这个包,我们便可以在空文件夹下生成一个项目的基础骨架。

dist  //发布目录,用于生产环境
src   //开发目录,开发时所需资源
|----dist  //测试环境目录
|     |----static
|             |----css  //编译打包后的css资源
|             |----js   //打包压缩后的js资源
|             |----imgs //测试环境图片资源
|----less  //开发所需less代码
|----js    //开发所需js代码
|    |----lib //库或框架资源
|----imgs  //开发所需图片资源
index.html    //开发页面
gulpfile.js
package.json
README.md

What a good idea~!

在学长的这个包中,主要构建了 gulp 配置,less 和测试文件的骨架。虽然再无更多内容,但这份构建基础骨架的灵感还是被我愉快的收走了——学前端的人很多,但大多都太缺工程化意识了。于是,这个灵感成为了不错突破口。

creatshare-app-init 脚手架孕育而生。

0

通过这篇文章,你能了解到:

  • 如何用 NodeJS 编写命令行工具?
  • 如何发布自己的 npm 包?
  • 笔者与 creatshare-app-init 的故事?

在本文中,或多或少出现过以下关键字,我的解释是:

  • 轮子:该词在前端开发日常用语中,表示一个基于原生代码实现,但并没有对前端行业产生积极意义的模块。虽然它的出现方便了一些人的使用,但更多的加大了我们的学习成本。
  • 项目:该词在前端领域常指一个服务于用户的软件立项。
  • 模块:creatshare-app-init 就是一个模块,是开发前端项目中的一个子集。正如汽车的各个部件一样,多个模块合理组装起来才是一辆汽车。

1

尝试解析源码,第一步,从模块根目录下的 package.json 来看。

"dependencies": {"commander": "^2.11.0"
},
"devDependencies": {"babel-plugin-transform-runtime": "^6.23.0","babel-preset-es2015": "^6.24.1","babel-preset-stage-2": "^6.24.1","babel-runtime": "^6.26.0","eslint-config-standard": "^10.2.1","eslint-plugin-import": "^2.8.0","eslint-plugin-node": "^5.2.1","eslint-plugin-promise": "^3.6.0","eslint-plugin-standard": "^3.0.1"
}

如上,dependencies 声明了模块上线时的依赖,devDependencies 声明了模块开发时的依赖。该模块在上线时,即 npm 包被用户用到时,只需要 commander 库。commander 库是 NodeJS 命令行接口开发的优选解决方案,受启发于 Ruby 的 commander。在解析 bin/index.js 源码时将详细拓展。

"name": "creatshare-app-init",
"version": "2.1.0",
"description": "CreatShare 实验室前端项目初始化工具",
"bin": {"cs": "bin/index.js"
},
"scripts": {"compile": "babel src/ -d lib/","prepublish": "npm run compile","eslint": "eslint src bin","test": "echo \"Error: no test specified\" && exit 1"
},

上面一段是 package.json 最开头的内容,字段详情如下:

  • name 字段:声明模块名称。特殊注意该字段不允许大写字母及空格的出现,且其与 version 字段形成了 npm 模块的唯一标识符。
  • version 字段:声明模块当前版本号。这里每当使用 npm publish 将模块发布到 npm 仓库中时,版本号都需要手动自增。
  • description 字段:对模块进行描述,同时有助于被检索。
  • bin 字段:npm 本身是通过 bin 属性配置一个或多个可解析到 PATH 路径下的可执行模块。模块若被全局安装,则 npm 会为 bin 中配置的文件在 bin 目录下创建一个软连接;模块若被局部安装,软连接会配置在项目内的 ./node_modules/.bin/目录下。
  • script 字段:定义模块的脚本配置。如,当我们在模块目录下使用 npm run compile 时,将自动执行 babel src/ -d lib/ 命令,进行 ECMAScript6 代码的转译。

2

刚刚提到 package.json 配置文件下的 bin 字段声明了 npm 在生成软连接时的配置。这就便是用户在安装好这个目录后,可以随时使用 cs 命令的出处。

我们又提到了该模块在非开发环境下只需用到 commander 模块,这个模块是 NodeJS 命令行接口开发的优选解决方案。

基于这俩点,我们就从 bin 字段所指向的 bin/index.js 聊起。

#!/usr/bin/env nodevar program = require('commander')
var cs = require('../lib/cs')program.allowUnknownOption().version('2.1.1').description('CreatShare 互联网实验室前端 Web App 项目脚手架').option('-e, --enjoy')program..command('create <dir>').description('创建一个新的 Web App 项目骨架').action(function (rootDir) {cs.create(rootDir)
})program.parse(process.argv)

就这么二十来行。因为我们要写的模块是要运行在命令行下的,就需要 #!/usr/bin/env node 语句来告诉系统使用 node 环境来运行我们的文件,必不可少。

在引入 commander 并将其赋值给 program 变量后,我们对其使用了如下方法:

  • .allowUnknownOption() 方法:
  • .version() 方法:用于设置命令程序的版本号。
  • .description() 方法:用于设置命令的描述。可以绑定在跟命令下,这里是 cs 命令;或绑定在子命令下,如 cs create <dir> 命令。
  • .option() 方法:定义命令的具体选项。
  • .command() 方法:定义命令的子命令,这里是 cs create <dir> 命令。
  • .action() 方法:用于设置命令执行的相关回调。这里绑定在 cs create <dir> 命令上,在使用该命令时触发执行回调函数。

代码最后的 process 为进程对象,是 NodeJS 运行时存在的众多全局变量之一。process 对象中的 argv 属性用来捕获命令行参数。

3

刚刚在 bin/index.js 里说明的 .action 回调函数绑定在 cs create <dir> 命令下。当我们使用该命令时,会触发 cs.create() 语句的执行,这就要提及我们引入的 lib/cs.js 文件了。

打住,第一节里展示的 package.json 中,script字段里有这么一条语句:"compile": "babel src/ -d lib/"。这是说明 lib/ 文件夹下的代码是通过 src/ 文件夹下的代码转译过来的,真正我们需要去关注的是 src/cs.js 文件。

为什么需要转译?src 里的 JavaScript 代码或多或少的使用到了 ECMAScript6 新特性,有些用户的 Node 环境并不一定能得到较好的解析。

src/cs.js 主要代码片段为:

let create = require('./create')
let path = require('path')
let distPath = path.join(__dirname, '/../dist')
let dist = process.cwd() + '/'/**
* [运行 create 命令]
* @return {[type]} [description]
*/
exports.create = (rootDir) => {console.log('\n项目目录开始创建\n')create.init(distPath, dist, rootDir)helpGuide()
}

不难理解,create 变量指向 cs create <dir> 所要执行的源代码;path 是 NodeJS 自带模块,提供文件目录解析功能。

最终 src/index.js 使用 exports.create 语句向外部暴露出 create 方法。bin/index.js 便可以将该方法通过 .action() 绑定到 cs create <dir> 命令上了。

4

精彩的来了。都说 ECMAScript6 的指定振奋人心,JavaScript 的魅力越来越大,这里便是一次体验 JavaScript 在 NodeJS 上的新玩法有趣之旅。

src/create.js 文件中,主要用到了 NodeJS 自带的 fs 文件模块,来生成新项目的基础架构。文件最后暴露出的 init 方法源码如下。

exports.init = (path, dist, rootDir) => {createRootDir(rootDir)// 从新目录开始新建项目dist = dist + rootDircopyDir(path, dist)
}

init 方法获取了 path 参数、dist 参数和 rootDir 参数。在该方法中,我们先将 rootDir 参数传入 createRootDir() 函数中创建项目根目录。

在哪里创建项目根目录呢?就在执行 cs 命令时的当前目录下:

const createRootDir = (rootDir) => {fs.access(process.cwd(), function (err) {if (err) {// 目录不存在时创建目录fs.mkdirSync(rootDir)}})
}

有了项目根目录,就要将模块下 dist/ 文件夹里的所有文件递归拷贝到根目录下。一个参数用来指向 dist/ 文件夹,另一个参数用来指向根目录,便可以开始递归复制。

/*** [初始化静态资源]* @param  {[type]} src  [初始化资源路径]* @param  {[type]} dist [当前终端所在目录]* @return {[type]}      [description]*/
const copyDir = (src, dist) => {fs.access(dist, function (err) {if (err) {// 目录不存在时创建目录fs.mkdirSync(dist)}_copy(null, src, dist)})function _copy (err, src, dist) {if (err) { throw err }fs.readdir(src, function (err, files) {if (err) { throw err }// 过滤不生成的文件miscFiles.forEach(function (v) {if (!files.includes(v)) returnfiles = files.filter(function (k) {return k !== v})})// 遍历目录中的文件files.forEach(function (path) {var _src = src + '/' + pathvar _dist = dist + '/' + pathfs.stat(_src, function (err, st) {if (err) { throw err }// 判断是文件还是目录if (st.isFile()) {fs.writeFileSync(_dist, fs.readFileSync(_src))} else if (st.isDirectory()) {// 当是目录是,递归复制copyDir(_src, _dist)}})})})}
}

fs 文件模块的具体内容推荐阅读阮一峰的开源电子书——《JavaScript 标准参考教程》中的“NodeJS”章节,来深入浅出 fs 模块的用法。

完美,这时我们就可以发布我们的脚手架包了。

5

如何发布一个 npm 包到 npm 仓库中,供其他人使用?当我们照着第一步,将 package.json 配置好后,其实模块的准备工作已经做好了。

还没有做的就是在域名为 npmjs.com 的官网上注册一个账号。这样,当我们直接在模块根目录使用 npm publish 命令的时候,输入正确的 npmjs.com 账号、密码,就能成功发布你的开源包了!

纵然读博文是一个有趣的体验,但也可以亲自动手试一试哦。

6

也就是说,酷炫的生成新项目骨架的来源,只是简单的递归复制该模块下的 dist/ 文件夹到新项目中。但我们需要关注的重点在于,dist/ 文件夹下,到底装了什么?

“初级 Web App 项目初始化工具”一说,也就名归有主了。dist/ 模板,也就是新项目的骨架如下。

.
├── .babelrc             # ES6 代码转义规则配置
├── .eslint.js           # JavaScript 代码规范
├── .gitignore           # Git 不跟踪的特殊文件
├── LICENSE              # 开源协议
├── README.md            # 项目介绍
├── material             # README.md 引用的图片库
├── package.json         # 项目配置文件
├── src                  # 源码开发目录
│   ├── favicon.ico      # 网页标题小图标
│   ├── html             # HTML 页面模板目录
│   ├── image            # 图片资源目录
│   ├── manifest.json    # 网络应用清单
│   ├── script           # 脚本文件资源目录
│   └── style            # 样式文件资源目录
├── webpack.config.js    # Webpack 多文件打包基础配置
├── webpack.dev.js       # Webpack 开发环境配置
├── webpack.prod.js      # Webpack 发布上线配置
└── yarn.lock            # yarn 包管理器的依赖说明

新项目骨架中默认推荐了:

  • 使用 Webpack 来打包多页面;
  • 使用 ESLint 来规范自己项目的 JavaScript 代码;
  • 使用 Babel 来编译使用 ECMAScript 新特性的 JavaScript 代码。
  • 使用 MIT 开源协议;
  • 源代码都放在 src/ 目录下;
  • src/ 目录要对不同的代码进行合理的分层。

End

现在的不足,是未来的畅想。

这个模块并不完美,一个健壮的命令还应该能支持足够多的参数,运行足够有意义的子命令。比如我们常用 man 命令来看另一个命令的使用手册,那要让用户能用到 man cs 命令,还需要我们在代码中加入 man 字段等等。。

我又为什么,这么热衷于分享这个轮子?

记得有一个前端群里曾有人问过:

“怎么没有 VueJS 的源码解析?”

时,我说过:

“大牛很忙,关注的是前端前沿,不写这些源码解析博文是个好事。

“当我们想有一个源码解析教程的时候,这是一个打开新世界的契机——未尝不使我们亲自来写,通过分享走向学习效率金字塔的最高层?”

这样的能力并不是人人都能具备,也不必要让人人都具备。我曾在大一傲气的说过“做最好的自己,影响该影响的人”,现在想起来除了有立刻找地洞钻进去的冲动外,反而还是觉得有一定的道理(笑。这时候允许我自称为一次“教主”,我们的理念是:

读文档,读文档,读文档。
写博客,写博客,写博客。

  • Hello,我是韩亦乐,现任本科软工男一枚。软件工程专业的一路学习中,我有很多感悟,也享受持续分享的过程。如果想了解更多或能及时收到我的最新文章,欢迎订阅我的个人微信号:韩亦乐。我的简书个人主页中,有我的订阅号二维码和 Github 主页地址;我的知乎主页 中也会坚持产出,欢迎关注。
  • 本文内部编号经由我的 Github 相关仓库统一管理;本文可能发布在多个平台但仅在上述仓库中长期维护;本文同时采用【知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议】进行许可。

造轮子之 npm i -g creatshare-app-init 源码浅析相关推荐

  1. 深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 前言 前一阵子,写了几篇 Android 启动优化的文章,主要是 ...

  2. 直播app系统源码通过CSS液体实现加载动画

    直播app系统源码通过CSS液体实现加载动画 首先我们要让元素能够旋转起来,可以使用transform中的rotate进行2D的360deg旋转. 紧接着我们可以通过CSS变量(–开头的形式)结合an ...

  3. 最新仿网易优选APP商城源码+Vue开发全家桶

    正文: 最新仿网易优选APP商城源码+Vue开发全家桶,源码采用Vue全家桶+mintUI+axios技术栈开发,只写了前端,后端采用网易商场抓包接口,也可以二次修改成自己的接口. 安装方法: 1.将 ...

  4. wemall app商城源码Android之支付宝接口公用函数

    wemall-mobile是基于WeMall的Android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之 ...

  5. wemall app商城源码Android之支付宝通知处理类

    wemall-mobile是基于WeMall的Android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之处 ...

  6. 2023 XL软件库App后端源码 可自定义易支付 完整版

    2023 XL软件库App后端源码 可自定义易支付 完整版 安装教程 先导入sql数据库,然后修改config.php 里边填数据库信息 再倒入app源码到iapp,打开源码main.iyu载入界面, ...

  7. android壁纸软件代码,android高清壁纸APP完整源码HD Wallpaper(服务端+客户端)

    描述 android高清壁纸APP完整源码HD Wallpaper with Material Design,包含android客户端源码.php+mysql服务端源码,带文档 "HD Wa ...

  8. 2022外卖霸王餐程序、外系统霸王餐H5/APP程序源码|美团/饿了么霸王餐系统 粉丝裂变 自带账单 在线支付提现等

    2022外卖霸王餐程序.外系统霸王餐H5/APP程序源码|美团/饿了么霸王餐系统 粉丝裂变 自带账单 在线支付提现等 外卖霸王餐系统程序/H5/APP源码 2022最新霸王餐程序 霸王餐程序/H5/A ...

  9. 小程序/app 商城 源码 发布(包括后台)

    小程序/app 商城 源码 发布(包括后台)无任何隐瞒 前端使用uniapp,后端使用thinkphp,开发简单好用的商城,可以生成为app,网站,公众号,小程序等, 这是一个直接可以商用的商城项目. ...

  10. 成品app直播源码,服务端与客户端传输视频文件

    成品app直播源码,服务端与客户端传输视频文件相关的代码 Server端 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARN ...

最新文章

  1. Linux环境下oracle client安装和配置
  2. LPC1768外部中断与GPIO中断
  3. oracle手动启动服务
  4. 重复制造--REM主数据
  5. bzoj1055 [HAOI2008]玩具取名 区间DP
  6. python docx 合并文档 图片_不再为处理PDF烦恼,python处理操作PDF全攻略
  7. 仅展示近三天的动态设置_抱歉,朋友仅展示最近三天的朋友圈
  8. 关于三星某些系列笔记本电脑无法安装Windows10的原因及解决办法
  9. RDS 设置 group_concat的长度限制 1024 改为 102400
  10. 8月第4周回顾:Delphi2009发布 CCIE增加英文面试
  11. div 中的i标签如何点击事件_前端优化:语义标签进化史
  12. windows字体:中文名,英文名
  13. Java课程设计 商品管理系统
  14. 论文阅读|Cascade R-CNN
  15. 高等数学知识点总结——导数定义及性质、微积分、泰勒展开、洛必达法则、函数单调性判断
  16. php下lua的运行,phpStudy中起用lua脚本
  17. caffe内CHECK_EQ等函数意义解释
  18. 【地图学】地图投影的定义和分类
  19. 奇瑞新能源的小车为何备受市场青睐?鲍思语这样解释
  20. 美国眼镜行业零售商Costco正式采购三井化学高折射率镜片单体MRTM

热门文章

  1. mysql spj_MySQL查询优化器--非SPJ优化--ORDERBY优化
  2. SAP PS 创建预留+采购申请
  3. 精品文章!精讲光模块的方方面面,收藏!
  4. 疯狂ios讲义之疯狂打飞机(2)
  5. Unity5.0 烘焙物体导入其他场景
  6. 工欲善其事,必先利其器-器
  7. MICCAI2019论文分享 PART①
  8. 蛋白质二级结构预测Linux,哪些蛋白质二级结构预测软件可以批量使用?
  9. HeadFirst 设计模式 4工厂模式(披萨店演变)
  10. 我的世界玩服务器虚拟内存高崩,服务端崩溃 - 崩溃解答 - MC百科社群 - MC百科|最大的Minecraft中文MOD百科...