什么是脚手架

在项目比较多而且杂的环境下,有时候我们想统一一下各个项目技术栈或者一些插件/组件的封装习惯,但是每次从零开发一个新项目的时候,总是会重复做一些类似于复制粘贴的工作,这是一个很头疼的事情,所以各种各样的脚手架应用而生。

脚手架也就是为了方便我们做一些重复的事情,快速搭建一个基本的完整的项目结构。例如:vue-cli, react-cli, express-generator

以vue-cli为例

1.全局安装vue-cli

npm install vue-cli -g

2.然后在终端中键入vue,vue init或者vue-init命令,会出现以下的命令说明:

xwkdeMacBook-Pro:bin xwk$ vue
Usage: vue <command> [options] Options:
-V, --version output the version number
-h, --help output usage information Commands:
init generate a new project from a template
list list available official templates
build prototype a new project
create (for v3 warning only)
help [cmd] display help for [cmd] xwkdeMacBook-Pro:bin xwk$ vue init
Usage: vue-init <template-name> [project-name] Options:
-c, --clone use git clone
--offline use cached template
-h, --help output usage information
Examples: # create a new project with an official template
$ vue init webpack my-project # create a new project straight from a github template
$ vue init username/repo my-project

可以根据这些命令说明,来快速生成一个项目骨架,例如:vue init webpack demo1

xwkdeMacBook-Pro:Practice xwk$ vue init webpack demo1

? Project name: demo1
? Project description: A Vue.js project
? Author xuweikang: <xuweikang@dajiazhongyi.com>
? Vue build standalone:
? Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests? No
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recomme
nded) npm vue-cli · Generated "demo1". # Installing project dependencies ...

如上图所示,在输入vue init指令的时候,会有一些选项让我们去选择,选择完成后在当前目录就会多出一个demo1的文件夹,这就是脚手架生成的项目骨架,到目前为止,已经成功的使用脚手架工具创建出了一个我们半自定义(一些自定义配置选项是我们在刚开始选择的那些,脚手架会将这些选项应用到初始化项目中)的项目。

vue-cli的原理分析

对于vue-cli的原理分析,其实无外乎有几个大点,首先从我刚开始在终端中输入vue/vue-init/vue init这些命令开始,为什么可以在终端中直接使用这些命令,这些命令的使用说明是怎么打印出来的,还有vue-cli是怎样在输入vue init webpack demo1命令后,成功的在当前目录创建出一个项目骨架的,这些项目是怎么来的。

  1. 可执行的npm包

    如果我们想让一个模块全局可执行,就需要把这个模块配置到PATH路径下,npm让这个工作变得很简单,通过在package.json文件里面配置bin属性,这样该模块在安装的时候,若是全局安装,则npm会为bin里面的文件在PATH目录下配置一个软链接,若是局部安装,则会在项目里面的node_modules/.bin目录下创建一个软链接,例如:
//package.json
{
"bin": {
"cvd": "bin/cvd"
}
}

当我们安装这个模块的时候,npm就会为bin下面的cvd文件在/usr/local/bin创建一个软链接。在Mac系统下,usr/local/bin这个目录,是一个已经包含在环境变量里的目录,可以直接在终端中执行这里的文件。

注意:windows下bin目录不一样,如果是直接在本地项目中进行包调试,可以通过npm link命令,将本项目的bin目录链接到全局目录里,这里面也可以看到对应的bin目录。

xwkdeMacBook-Pro:vue-cli-demo1 xwk$ npm link
npm WARN vue-cli-demo1@1.0.0 No description
npm WARN vue-cli-demo1@1.0.0 No repository field. audited 1 package in 1.35s
found 0 vulnerabilities /usr/local/bin/cvd -> /usr/local/lib/node_modules/vue-cli-demo1/bin/cvd
/usr/local/bin/cvd-init -> /usr/local/lib/node_modules/vue-cli-demo1/bin/cvd-init
/usr/local/lib/node_modules/vue-cli-demo1 -> /Users/admin/Project/vue-cli-demo1

到目前为止可以解释了为什么我们在全局install了vue-cli后,可以直接使用vue/vue-init等命令。

2.vue-cli源码分析

找到vue-cli源码进行分析,有两种方法,可以直接去找刚刚安装的脚手架的位置,这里是全局安装的,mac会定位到/usr/local/lib/node_modules/vue-cli,或者直接看vue-cli的仓库源码,点击 这里

有了上面的分析,直接找到package.json,可以看到:

{
"bin": {
"vue": "bin/vue",
"vue-init": "bin/vue-init",
"vue-list": "bin/vue-list"
}
}

这里面定义了3个可执行文件命令,vue,vue-init和vue-list,分别对应到了bin目录下的vue,vue-init,vue-lsit文件,这里只分析下第一个和第二个文件。

bin/vue

#!/usr/bin/env node //声明下该文件要用node格式打开

const program = require('commander') //ti大神的nodejs命令行库

program
.version(require('../package').version) //取包的版本为当前版本
.usage('<command> [options]') //定义使用方法
.command('init', 'generate a new project from a template') //有一个init方法,并且对其进行描述
.command('list', 'list available official templates') //有一个list方法,并且对其进行描述
.command('build', 'prototype a new project') //有一个build方法,并且对其进行描述
.command('create', '(for v3 warning only)') //有一个create方法,并且对其进行描述 program.parse(process.argv) //执行

效果如下:

xwkdeMacBook-Pro:bin xwk$ vue
Usage: vue <command> [options] Options:
-V, --version output the version number
-h, --help output usage information Commands:
init generate a new project from a template
list list available official templates
build prototype a new project
create (for v3 warning only)
help [cmd] display help for [cmd]

bin/vue-init

/**
* Usage.
*/ program
.usage('<template-name> [project-name]')
.option('-c, --clone', 'use git clone')
.option('--offline', 'use cached template') /**
* Help.
*/ program.on('--help', () => {
console.log(' Examples:')
console.log()
console.log(chalk.gray(' # create a new project with an official template'))
console.log(' $ vue init webpack my-project')
console.log()
console.log(chalk.gray(' # create a new project straight from a github template'))
console.log(' $ vue init username/repo my-project')
console.log()
})

这部分主要是声明一些命令和使用方法介绍,其中chalk 是一个可以让终端输出内容变色的模块。

下面这部分主要是一些变量的获取,定义项目名称,输出路径,以及本地存放模板的路径位置

/**
* Settings.
*/
//vue init 命令后的第一个参数,template路径
let template = program.args[0]
//template中是否带有路径标识
const hasSlash = template.indexOf('/') > -1
//第二个参数是项目名称,如果没声明的话或者是一个“.”,就取当前路径的父目录名字
const rawName = program.args[1]
const inPlace = !rawName || rawName === '.'
const name = inPlace ? path.relative('../', process.cwd()) : rawName
//输出路径
const to = path.resolve(rawName || '.')
const clone = program.clone || false //存放template的地方,用户主目录/.vue-templates ,我这里是/Users/admin/.vue-templates/
const tmp = path.join(home, '.vue-templates', template.replace(/[\/:]/g, '-'))
//如果是离线状态,模板路径就取本地的
if (program.offline) {
console.log(`> Use cached template at ${chalk.yellow(tildify(tmp))}`)
template = tmp
}

下面是一些对于项目初始化简单的问答提示,其中inquirer 是一个node在命令行中的问答模块,你可以根据答案去做不同的处理

if (inPlace || exists(to)) {
inquirer.prompt([{
type: 'confirm',
message: inPlace
? 'Generate project in current directory?'
: 'Target directory exists. Continue?',
name: 'ok'
}]).then(answers => {
if (answers.ok) {
run()
}
}).catch(logger.fatal)
} else {
run()
}

接下来是下载模板的具体逻辑,如果是本地模板,则直接生成


function run () {
// check if template is local
if (isLocalPath(template)) {
//获取模版地址
const templatePath = getTemplatePath(template)
if (exists(templatePath)) {
//开始生成模板
generate(name, templatePath, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
} else {
logger.fatal('Local template "%s" not found.', template)
}
} else {
checkVersion(() => {
//路径中是否包含 ‘/’,如果包含 ‘/’,则直接去指定模板路径去下载
if (!hasSlash) {
// use official templates
//生产仓库里面的模板路径
const officialTemplate = 'vuejs-templates/' + template
if (template.indexOf('#') !== -1) {
//下载仓库分支代码
downloadAndGenerate(officialTemplate)
} else {
if (template.indexOf('-2.0') !== -1) {
//如果存在 ‘-2.0’ 标识,则会输出模板废弃的警告并退出
warnings.v2SuffixTemplatesDeprecated(template, inPlace ? '' : name)
return
} // warnings.v2BranchIsNowDefault(template, inPlace ? '' : name)
//开始下载
downloadAndGenerate(officialTemplate)
}
} else {
downloadAndGenerate(template)
}
})
}
}

vue-init源码的最后一部分,是对downloadAndGenerate方法的声明,是下载并在本地生产项目的具体逻辑。

download是download-git-repo模块的方法,是来做从git仓库下载代码的,

//第一个参数是仓库地址,如果指定分支用“#”分开,第二个为输出地址,第三个为是否clone,为flase的话就下载zip,
//第四个参数是回调
download('flipxfx/download-git-repo-fixture#develop', 'test/tmp',{ clone: true }, function (err) {
console.log(err ? 'Error' : 'Success')
})
function downloadAndGenerate (template) {
//ora库在终端中显示加载动画
const spinner = ora('downloading template')
spinner.start()
// Remove if local template exists
//如果有相同文件夹,则覆盖删除
if (exists(tmp)) rm(tmp)
download(template, tmp, { clone }, err => {
spinner.stop()
if (err) logger.fatal('Failed to download repo ' + template + ': ' + err.message.trim())
//生成个性化内容
generate(name, tmp, to, err => {
if (err) logger.fatal(err)
console.log()
logger.success('Generated "%s".', name)
})
})
}

lib/generate.js

至此,还差最后一步,即generate方法的定义,这个方法在lib/generate.js中,主要作用是用于模板生成

/**
* Generate a template given a `src` and `dest`.
*
* @param {String} name
* @param {String} src
* @param {String} dest
* @param {Function} done
*/ module.exports = function generate (name, src, dest, done) {
//设置meta.js/meta.json配置文件的name字段和auther字段为项目名和git用户名,同时还设置了校验npm包名的方法属性
const opts = getOptions(name, src)
//指定Metalsmith的模板目录路径
const metalsmith = Metalsmith(path.join(src, 'template'))
//将metalsmith的默认metadata和新增的3个属性合并起来
const data = Object.assign(metalsmith.metadata(), {
destDirName: name,
inPlace: dest === process.cwd(),
noEscape: true
})
//注册一些其他的渲染器,例如if_or/template_version
opts.helpers && Object.keys(opts.helpers).map(key => {
Handlebars.registerHelper(key, opts.helpers[key])
}) const helpers = { chalk, logger } if (opts.metalsmith && typeof opts.metalsmith.before === 'function') {
opts.metalsmith.before(metalsmith, opts, helpers)
} //metalsmith做渲染的时候定义了一些自定义插件
//askQuestions是调用inquirer库询问了一些问题,并把回答结果放到metalsmithData中
//生产静态文件时删除一些不需要的文件,meta文件的filters字段中进行条件设置
//开始生产文件
metalsmith.use(askQuestions(opts.prompts))
.use(filterFiles(opts.filters))
.use(renderTemplateFiles(opts.skipInterpolation)) if (typeof opts.metalsmith === 'function') {
opts.metalsmith(metalsmith, opts, helpers)
} else if (opts.metalsmith && typeof opts.metalsmith.after === 'function') {
opts.metalsmith.after(metalsmith, opts, helpers)
} metalsmith.clean(false)
.source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
.destination(dest)
.build((err, files) => {
done(err)
if (typeof opts.complete === 'function') {
const helpers = { chalk, logger, files }
opts.complete(data, helpers)
} else {
logMessage(opts.completeMessage, data)
}
}) return data
}

为什么vue init命令也可以?

按照之前所说的,如果要使用命令必须要在package.json 里面的bin字段进行说明,看到只有vue,vue-init,vue-list,如果是vue init 是使用了vue的命令的话,那么init肯定是作为一个参数传入的,bin/vue里面也并没有关于对init参数的具体执行,只有一些简单的参数说明。

也可以注意到,在命令行中敲入vue init和vue-init 是同样的效果。其实,两个命令是同一个逻辑,具体要看commander readme里面有这样一段话:

When .command() is invoked with a description argument, no .action(callback) should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like git(1) and other popular tools.

The commander will try to search the executables in the directory of the entry script (like ./examples/pm) with the name program-command, like pm-install, pm-search.

总结一下

前端脚手架的开发,依靠的是npm的全局包安装,在package.json里面的bin字段指定命令名字和对应的脚本处理,以键值对的方式声明。npm包在全局安装的时候,npm会将bin里面的命令,在PATH目录里面创建一个软连接,使得可以直接在终端里使用这些指令。如果是本地开发的npm包,可以通过npm link手动链接到PATH目录。

对于vue-cli,会在初始化的时候去模板仓库下载对应的模板,然后通过收录一些问题,把这些用户定制化的信息更新到meta.js中,metalsmith做渲染的时候,拿到meta.js里面的配置数据,生成一个最终的静态骨架。

发布到npm

在本地开发完脚手架后,需要把对应的包放到npm仓库中供其他人下载使用。

首先去npm仓库注册一个npm账号,

然后在本地包目录下登陆npm, npm login

最后发布, npm publish

基于vue的脚手架开发与发布到npm仓库的更多相关文章

  1. vue-calendar 基于 vue 2.0 开发的轻量,高性能日历组件

    vue-calendar-component 基于 vue 2.0 开发的轻量,高性能日历组件 占用内存小,性能好,样式好看,可扩展性强 原生 js 开发,没引入第三方库 Why Github 上很多 ...

  2. 从零开始把项目发布到NPM仓库中心

    从零开始把项目发布到NPM仓库中心 前期准备 注册账号 https://www.npmjs.com/signup 网易邮箱注册失败,用QQ邮箱成功 使用npm 命令注册(建议使用网页): npm ad ...

  3. 如何将项目发布到npm仓库

    有时候,我们希望将项目里的模块提升为公共模块,以便其他项目也能使用.在前端可以将模块发布到npm仓库,这样所有项目都可以通过 npm install youProject 使用模块了. 这个过程很简单 ...

  4. vue组件从开发到发布

    组件化是前端开发非常重要的一部分,从业务中解耦出来,可以提高项目的代码复用率.更重要的是我们还可以打包发布,俗话说集体的力量是伟大的,正因为有许许多多的开源贡献者,才有了现在的世界. 不想造轮子的工程 ...

  5. vue封装第三方插件并发布到npm

    前言 写此文前特意google了一下,因为有较详细的开发教程我再写意义不大,有把插件封装成组件的教程,有把自己的组件封住成插件的教程,本文主要说明如何把第三方的插件封装成vue插件,简化配置,一键安装 ...

  6. vue-cli4.0 基于 antd-design-vue 二次封装发布到 npm 仓库

    1. 安装 cli npm install -g @vue/cli vue create winyh-ui 2.安装 antd-design-vue cnpm i ant-design-vue --s ...

  7. 基于Vue全家桶开发的前端组件管理平台

    项目背景 项目背景是外包类建站公司里,设计环节沉淀了大量可复用组件,设计师往往只需要微调组件就拼凑出页面,交付给前端,理论上这些组件在前端也可以复用,但实际上前端每次都要重新实现整个页面,浪费很多人力 ...

  8. 基于VUE,VUX组件开发的网易新闻页面搭建过程

    根据妙味课堂上的一个教程练习总结,供自己复习用 一.功能介绍 一个网易新闻客户端的浏览页面,通过网易新闻的api接口实时获取新闻数据,用vux搭建样式框架,以轮播图,文字滚动,图文列表等形式把内容展示 ...

  9. Vue搭建组件库并发布到 npm

    https://www.jianshu.com/p/72d303449abc

随机推荐

  1. vue组件传参的方法--bus事件总线

    定义:事件总线是实现vue任意组件之前传递参数的一种编程技巧,本质上就是组件的自定义事件.事件总线有很多种写法,具体的思路就是创造一个大家都可以访问到的公共的属性,在这个公共的属性上面可以调用$on, ...

  2. conda和pip加速参考

    conda install和创建虚拟环境下载慢,可以修改/root/.condarc文件: vim /root/.condarc 各系统都可以通过修改用户目录下的 .condarc 文件.Window ...

  3. 从0到1搭建一款Vue可配置视频播放器组件(Npm已发布)

    前言 话不多说,这篇文章主要讲述如何从0到1搭建一款适用于Vue.js的自定义配置视频播放器.我们平时在PC端网站上观看视频时,会看到有很多丰富样式的视频播放器,而我们自己写的video标签样式却是那 ...

  4. Leetcode----<Diving Board LCCI>

    题解如下: public class DivingBoardLCCI { /** * 暴力解法,遍历每一种可能性 时间复杂度:O(2*N) * @param shorter * @param long ...

  5. docker compose 部署 minio

    1.docker-compose.yaml 文件如下: version: '3' services: minio: image: minio/minio:latest # 原镜像`minio/mini ...

  6. 图片64base转码与解码

    场景一:图片转码成base64,传输,接收后解码成png等格式图片 import base64 # 读取图片,转换为base64编码格式 with open("F:\Archer\pictu ...

  7. P6622 信号传递 做题感想

    题目链接 前言 在这里分享两种的做法. 一种是我第一直觉的 模拟退火.(也就是骗分) 还有一种是看题解才搞懂的神仙折半搜索加上 dp . 模拟退火 众所周知,模拟退火 是我这种没脑子选手用来骗分的好算 ...

  8. C++ 模板和泛型编程(掌握Vector等容器的使用)

    1. 泛型 泛型在我的理解里,就是可以泛化到多种基本的数据类型,例如整数.浮点数.字符和布尔类型以及自己定义的结构体.而容器就是提供能够填充任意类型的数据的数据结构.例如vector就很类似于pyth ...

  9. 一个月后,我们又从 MySQL 双主切换成了主 - 从!

    这是悟空的第 157 篇原创文章 官网:www.passjava.cn 你好,我是悟空. 一.遇到的坑 一个月前,我们在测试环境部署了一套 MySQL 高可用架构,也就是 MySQL 双主 + Kee ...

  10. Josephus问题(Ⅱ)

    题目描述 n个人排成一圈,按顺时针方向依次编号1,2,3-n.从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子.这样不断循环下去,圈子里的人将不断减少.最终一定会剩下一个人 ...