基于vue的脚手架开发与发布到npm仓库

vue

脚手架">什么是脚手架

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

脚手架也就是为了方便我们做一些重复的事情,快速搭建一个基本的完整的项目结构。例如: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仓库 的全部内容, 来源链接: utcz.com/z/376294.html

回到顶部