摘要:然后配置一些脚本来执行 npm version,这样当你包版本有更新后 push 到 GitHub repo,就会触发 travis 自动发包到 npm。[4] travis 自动发布包到 npm:  https://github.com/release-it/release-it/blob/master/docs/npm.md。

为什么我们需要一套脚手架

为什么我们需要一套脚手架,它能帮助我们解决哪些痛点问题。

前端项目配置越来越繁琐、耗时,重复无意义的工作 项目结构不统一、不规范 前端项目类型繁多,不同项目不同配置,管理成本高 脚手架也可以是一套命令集,不只用来创建项目

那么为什么不用一些开源框架自身的 CLI 工具,需要自己开发呢,这里仁者见仁智者见智,我个人建议就是对于中型团队以上需要自己维护一套脚手架,因为可控性高,能满足团队特定需求的研发。

如何按照开源要求开发一个前端脚手架?

下面是我们常见的前端开源目录结构

脚手架的设计

思路

解耦:脚手架与模板分离 脚手架负责构建流程,通过命令行与用户交互,获取项目信息 模板负责统一项目结构、工作流程、依赖项管理 脚手架需要检测模板的版本是否有更新,支持模板的删除与新建 ……

流程图

代码讲解

目录结构

配置 Git hook

首先进行开发前的准备工作,来保证你代码的质量。

Husky + Lint-staged

通过 Git hook 完成 commitlint、ESLint、prettiter 等,具体配置我后面会给源码,有兴趣的可以自己搜索下。

// package.json

"husky": {

"hooks": {

"pre-commit": "lint-staged",

"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"

}

},

"lint-staged": {

"**/*.js": [

"eslint --fix",

"prettier --write",

"git add"

]

}

package.json 下的 bin 字段

bin:配置内部命令对应的可执行文件位置,配置命令后,npm 会寻找到对应的可执行文件,然后在 node_modules/.bin 目录下建立对应的符号链接。

由于 node_modules/.bin 会在运行时候加入到系统的环境变量,因此我们可以通过 npm 调用命令来执行脚本。

所有 node_modules/.bin 目录下的命令都可以通过 npm run [命令] 执行。

所以我们需要在 package.json 配置入口

"bin": {

"easy": "bin/easy.js"

}


npm link 本地调试

这里介绍下开发脚手架的调试方法。npm link 官网使用介绍。使用方法:

// cd 到你项目的bin目录(脚本)下

$ npm link

去掉 link 也非常方便:

npm unlink linkname

bin 目录下的入口文件


#!/usr/bin/env node


const program = require('commander'); // 命令行工具

const chalk = require('chalk'); // 命令行输出美化

const didYouMean = require('didyoumean'); // 简易的智能匹配引擎

const semver = require('semver'); // npm的语义版本包

const enhanceErrorMessages = require('../lib/util/enhanceErrorMessages.js');

const requiredNodeVersion = require('../package.json').engines.node;


didYouMean.threshold = 0.6;


function checkNodeVersion(wanted, cliName) {

// 检测node版本是否符合要求范围

if (!semver.satisfies(process.version, wanted)) {

console.log(

chalk.red(

'You are using Node ' +

process.version +

', but this version of ' +

cliName +

' requires Node ' +

wanted +

'.\nPlease upgrade your Node version.'

)

);

// 退出进程

process.exit(1);

}

}


// 检测node版本

checkNodeVersion(requiredNodeVersion, '@easy/cli');


program

.version(require('../package').version, '-v, --version') // 版本

.usage('<command> [options]'); // 使用信息


// 初始化项目模板

program

.command('create <template-name> <project-name>')

.description('create a new project from a template')

.action((templateName, projectName, cmd) => {

// 输入参数校验

validateArgsLen(process.argv.length, 5);

require('../lib/easy-create')(lowercase(templateName), projectName);

});


// 添加一个项目模板

program

.command('add <template-name> <git-repo-address>')

.description('add a project template')

.action((templateName, gitRepoAddress, cmd) => {

validateArgsLen(process.argv.length, 5);

require('../lib/add-template')(lowercase(templateName), gitRepoAddress);

});


// 列出支持的项目模板

program

.command('list')

.description('list all available project template')

.action(cmd => {

validateArgsLen(process.argv.length, 3);

require('../lib/list-template')();

});


// 删除一个项目模板

program

.command('delete <template-name>')

.description('delete a project template')

.action((templateName, cmd) => {

validateArgsLen(process.argv.length, 4);

require('../lib/delete-template')(templateName);

});


// 处理非法命令

program.arguments('<command>').action(cmd => {

// 不退出输出帮助信息

program.outputHelp();

console.log(` ` + chalk.red(`Unknown command ${chalk.yellow(cmd)}.`));

console.log();

suggestCommands(cmd);

});


// 重写commander某些事件

enhanceErrorMessages('missingArgument', argsName => {

return `Missing required argument ${chalk.yellow(`<${argsName}>`)}`;

});


program.parse(process.argv); // 把命令行参数传给commander解析


// 输入easy显示帮助信息

if (!process.argv.slice(2).length) {

program.outputHelp();

}


// easy支持的命令

function suggestCommands(cmd) {

const avaliableCommands = program.commands.map(cmd => {

return cmd._name;

});

// 简易智能匹配用户命令

const suggestion = didYouMean(cmd, avaliableCommands);

if (suggestion) {

console.log(` ` + chalk.red(`Did you mean ${chalk.yellow(suggestion)}?`));

}

}


function lowercase(str) {

return str.toLocaleLowerCase();

}


function validateArgsLen(argvLen, maxArgvLens) {

if (argvLen > maxArgvLens) {

console.log(

chalk.yellow(

'\n Info: You provided more than argument. the rest are ignored.'

)

);

}

}

其他代码就不贴了我会给出源码链接,下面分享一下几个有意思的点。建议大家有兴趣的跟着敲一遍,有很多小细节需要注意。

发布脚本


// script/release.js


const { execSync } = require('child_process');

const semver = require('semver');

const inquirer = require('inquirer');


const currentVerison = require('../package.json').version;


const release = async () => {

console.log(`Current easy cli version is ${currentVerison}`);

const releaseActions = ['patch', 'minor', 'major'];

const versions = {};

// 生成预发布版本标示

releaseActions.map(r => (versions[r] = semver.inc(currentVerison, r)));

const releaseChoices = releaseActions.map(r => ({

name: `${r} (${versions[r]})`,

value: r

}));

// 选择发布方式

const { release } = await inquirer.prompt([

{

name: 'release',

message: 'Select a release type',

type: 'list',

choices: [...releaseChoices]

}

]);

// 优先自定义版本

const version = versions[release];

// 二次确认发布

const { yes } = await inquirer.prompt([

{

name: 'yes',

message: `Confirm releasing ${version}`,

type: 'confirm'

}

]);

if (yes) {

execSync(`standard-version -r ${release}`, {

stdio: 'inherit'

});

}

};


release().catch(err => {

console.error(err);

process.exit(1);

});


npm version 与 tag

官网关于 npm version 的介绍:

https://docs.npmjs.com/cli/version.html

如果不熟悉 Node 语义化版本可以阅读:

https://semver.org/lang/zh-CN/


npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]


'npm [-v | --version]' to print npm version

'npm view <pkg> version' to view a package's published version

'npm ls' to inspect current package/dependency versions


其实我们自己使用 npn publish,最终执行的还是 npm version 下命令。

官网关于 npm-dist-tag 的介绍:

npm-dist-tag [1]

npm install <name>@<tag>

npm install --tag <tag>

npm 也有 tag 的概念,一般情况下我们不会指定 tag,这个时候默认使用的就是 latest 这个 tag,所有的发布与安装都是最新的正式版本,如果指定 tag 之后,我们可以在这个 tag 上发布一个新的版本,用户安装时候也可以指定这个 tag 来进行安装,你可以简单理解 tag 类型 git 中的 branch。

常用的一些关于 tag 的命令:

# 查看当前的tag和对应的version。

npm dist-tag ls


# 查看my-package发布过的所有版本号。

npm view my-package versions


# 给my-package设置tag,对应到版本version。

npm dist-tag add my-package@version tag

如果一不小心把测试版发布成了正式版?发布之前我们是这样的:

latest: 1.0.0

next: 1.0.0-alpha.0

错误的把 1.0.0-alpha.1 直接 npm publish:

latest: 1.0.0-alpha.1

next: 1.0.0-alpha.0

解决方法:

# 把原来的1.0.0设置成最新的正式版

$ npm dist-tag add [email protected] latest


# 把1.0.0-alpha.1更新到最新的测试版

$ npm dist-tag add [email protected] next

npm publish 一个包

1. 创建一个 npm 账户 2. cd 到你需要发布的 repo 仓库下, 记得切换到 npm 源(或者公司内网自建源) 3. npm login,需要输入用户名、密码、邮箱 4. npm publish

集成 CI(Travis CI)自动发布

每次手动发布太 low 了,要是可以自动发布就好了。

Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 GitHub/GitLab 等上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。

持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码“集成”到主干。

简单理解就是:它的作用是自动帮你做好从代码测试到发布的一系列流程,配合版本控制使用的话可以设置成每一次 push 都自动进行一次集成,保证代码的正确性。

注意现在 GitHub 也出了集成工具,感兴趣的可以去体验下。

如果你的项目是在 GitHub 并且是开源的,推荐使用这个 org [2]

使用 GitHub 进行登录 Travis CI,完成一些授权工作,Travis CI 才能监听到你的 GitHub 项目代码的变化。

Travis CI 要求你项目的根目录必须有一个配置文件 .travis.yml 文件,这是一个配置文件,指定 travis 的行为,该文件还必须保存在 GitHub 的仓库。一旦有新的 push,travis 就会找到这个文件进行执行。

关于 travis 更多使用推荐阅读 官网 [3] ,这里主要讲下利用  travis 自动发布包到 npm [4] Continuous Integration environments [5]

下面是一个 .travis.yml 配置文件:

language: node_js

node_js:

- '8'

cache:

directories:

- node_modules

install:

- npm install

script:

- npm run lint

deploy:

provider: npm

email: "$NPM_EMAIL"

api_key: "$AUTH_TOKEN"

skip_cleanup: true

on:

branch: master

# after_success:

然后在你的 travis 上选择需要开启 CI 的项目。

配置对应环境变量到该仓库下如:

环境变量名格式必须为“大写字母_大写字母”格式。

token 生成也非常简单, 官网 [6] 介绍,可以直接在你的 npm 账户下的 tokens 页面手动生成或者通过 npm 命令行生成。

# 切换到npm源下, 登陆npm

npm login


# 生成token, npm可以指定生成token的权限(只读或者可读可写)

npm token create

然后配置一些脚本来执行 npm version,这样当你包版本有更新后 push 到 GitHub repo,就会触发 travis 自动发包到 npm。

DEMO

源码链接 [7]

感兴趣的童鞋希望自己跟着写一遍,代码量适中,有问题的可以加我微信交流,微信号:xyzxiaozhongge,备注即可。对您有帮助的可以推荐给身边的童鞋哈,写的不好的地方欢迎斧正。

如果有点用处不妨关注一下。

References

[1] npm-dist-tag:  https://docs.npmjs.com/cli/dist-tag

[2] org:  https://docs.travis-ci.com/

[3] 官网:  https://docs.travis-ci.com/

[4] travis 自动发布包到 npm:  https://github.com/release-it/release-it/blob/master/docs/npm.md

[5] Continuous Integration environments:  https://github.com/release-it/release-it/blob/master/docs/ci.md#npm

[6] 官网:  https://docs.npmjs.com/cli/token

[7] 源码链接:  https://github.com/NuoHui/easy-cli

相关文章