node.js 自定义项目脚手架
是不是觉得那些会写脚手架的人都是大佬,而自己只会写写页面,调调样式。脚手架太高级了,自己会用就行了。天天都在接触像npm包管理工具,vue-cli,webpack等等。要是自己也能写一个就太棒了,可以减少很多的重复劳动,想想就激动,那么请往下看。技术准备:必备nodejsgithub 账号(远程托管仓库)优化commander模块(命令行参数处理模块)co 模块(异步流程控制模块)co-promp
是不是觉得那些会写脚手架的人都是大佬,而自己只会写写页面,调调样式。脚手架太高级了,自己会用就行了。天天都在接触像npm包管理工具,vue-cli,webpack等等。要是自己也能写一个就太棒了,可以减少很多的重复劳动,想想就激动,那么请往下看。
准备工作:
必备项
- nodejs
- github 账号
- npm 账号
优化项
- commander模块(命令行参数处理模块)
- co 模块(异步流程控制模块)
- co-prompt 模块(消息提示模块)
- chalk 模块(输出字体颜色模块)
常用的node API
1. path(路径)
path 模块提供了一些实用工具,用于处理文件和目录的路径。 可以使用以下方式访问它:
const path = require(‘path’);
path.resolve([…paths]) 方法会将路径或路径片段的序列解析为绝对路径。
path.resolve('/目录1/目录2', './目录3');
// 返回: '/目录1/目录2/目录3'
path.resolve('/目录1/目录2', '/目录3/目录4/');
// 返回: '/目录3/目录4'
path.resolve('目录1', '目录2/目录3/', '../目录4/文件.gif');
// 如果当前工作目录是 /目录A/目录B,
// 则返回 '/目录A/目录B/目录1/目录2/目录4/文件.gif'
path.dirname(path) 方法会返回 path 的目录名
path.dirname('/目录1/目录2/目录3');
// 返回: '/目录1/目录2'
path.join([…paths]) 方法会将所有给定的 path 片段连接到一起(使用平台特定的分隔符作为定界符),然后规范化生成的路径。
path.join('/目录1', '目录2', '目录3/目录4', '目录5', '..');
// 返回: '/目录1/目录2/目录3/目录4'
path.join('目录1', {}, '目录2');
// 抛出 'TypeError: Path must be a string. Received {}'
2.fs(文件系统)
fs 模块可用于与文件系统进行交互(以类似于标准 POSIX 函数的方式)。
要使用此模块:
const fs = require(‘fs’);
fs.mkdir(path[, options], callback) 异步地创建目录。
// 创建 `/目录1/目录2/目录3`,不管 `/目录1` 和 `/目录1/目录2` 是否存在。
fs.mkdir('/目录1/目录2/目录3', { recursive: true }, (err) => {
if (err) throw err;
});
fs.mkdirSync(path[, options]) 同步地创建目录。
// 创建 `/目录1/目录2/目录3`,不管 `/目录1` 和 `/目录1/目录2` 是否存在。
fs.mkdir('/目录1/目录2/目录3', { recursive: true }, (err) => {
if (err) throw err;
});
fs.open(path[, flags[, mode]], callback) 异步地打开文件。
fs.open('myfile', 'r', (err, fd) => {
if (err) {
throw err;
}
readMyData(fd);
});
fs.opendir(path[, options], callback) 异步地打开文件。
fs.open('myfile', 'r', (err, fd) => {
if (err) {
throw err;
}
readMyData(fd);
});
fs.readFile(path[, options], callback) 异步地读取文件的全部内容。。
fs.readFile('文件名', (err, data) => {
if (err) throw err;
console.log(data);
});
fs.writeFile(file, data[, options], callback)
当 file 是文件名时,则异步地写入数据到文件(如果文件已存在,则覆盖文件)
当 file 是文件描述符时,则其行为类似于直接调用 fs.write()(建议使用)
const data = new Uint8Array(Buffer.from('Node.js 中文网'));
fs.writeFile('文件.txt', data, (err) => {
if (err) throw err;
console.log('文件已被保存');
});
3.process(进程)
process.argv 属性会返回一个数组,其中包含当 Node.js 进程被启动时传入的命令行参数。 第一个元素是 process.execPath。 如果需要访问 argv[0] 的原始值,则参见 process.argv0。 第二个元素是正被执行的 JavaScript 文件的路径。 其余的元素是任何额外的命令行参数。
例如:
// 执行命令
$ node process-args.js 参数1 参数2 参数3
// 打印 process.argv。
process.argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});
// 输出
/usr/local/bin/node
/Users/mjr/work/node/process-args.js
参数1
参数2
参数3
process.chdir(directory) process.chdir() 方法变更 Node.js 进程的当前工作目录。
console.log(`Starting directory: ${process.cwd()}`);
try {
process.chdir('/tmp');
console.log(`New directory: ${process.cwd()}`);
} catch (err) {
console.error(`chdir: ${err}`);
}
rocess.exit([code]) 方法以退出状态 code 指示 Node.js 同步地终止进程。 如果省略 code,则使用成功代码 0 或 process.exitCode 的值(如果已设置)退出。
// 使用1为失败退出
process.exit(1);
上面的API基本有异步就会有同步,就不列举过多,接下来就是实战生成文件目录和文件内容,直接用以前的ts项目为例。
const fs = require("fs");
const path = require("path");
const basePath = path.resolve(__dirname, "./");
const dirName = process.argv[2];
if (!dirName) {
console.log("文件夹名称不能为空!");
console.log("示例:npm run tep ${capPirName}");
process.exit(0);
}
const capPirName = camelCase(dirName);
//转驼峰命名
function camelCase(string) {
string = string.substring(0, 1).toUpperCase() + string.substring(1);
return string.replace(/-([a-z])/g, function(all, letter) {
return letter.toUpperCase();
});
}
/**
* @msg: vue页面模版
*/
const VueTep = `<template>
<div class="${dirName}-wrap">
{{pageInfo.name}}
</div>
</template>
<script lang="ts" src="./${dirName}.ts"></script>
<style lang="scss" scoped="">
@import "./${dirName}.scss";
</style>`;
// ts 模版
const tsTep = `import { Component, Vue } from "vue-property-decorator";
import { Getter, Action } from "vuex-class";
@Component({})
export default class ${capPirName} extends Vue {
@Getter ${dirName}.author
@Action GET_DATA_ASYN
private pageInfo: object = {
name: "${dirName}"
}
public created() {
//
}
}`;
// scss 模版
const scssTep = `@import "@/assets/css/var";
.${dirName}-wrap {
width: 100%;
}`;
// interface 模版
const interfaceTep = `// VUEX ${dirName}.State 参数类型
export interface ${capPirName}State {
${dirName}Data?: object
}`;
// vuex 模版
const vuexTep = `import { ${capPirName}State } from "../${dirName}.interface";
import { GetterTree, MutationTree, ActionTree } from 'vuex'
const namespace = "${capPirName}";
const state: ${capPirName}State = {
${dirName}Data: {}
}
export default {
name: namespace,
namespaced: true,
state
}`;
fs.mkdirSync(`${dirName}`); // mkdir
process.chdir(`${dirName}`); // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep); // vue
fs.writeFileSync(`${dirName}.ts`, tsTep); // ts
fs.writeFileSync(`${dirName}.scss`, scssTep); // scss
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep); // interface
fs.writeFileSync("module.ts", vuexTep); // vuex
process.exit(0);
复制上面的代码,新建temp.js文件,粘贴上面的代码,在存放的目录下打开命令行工具 执行 node temp.js <文件名>
文件、目录生成没问题了,那要是自己的脚手架有第三方依赖或者还有其他操作怎么办呢?继续往下~
4.child_process(子进程)
创建异步的进程
const exec = require('child_process').exec;
exec("npm i", (err, stdout, stderr) => {
if (err){
console.log(err);
console.warn(new Date(), '命令执行失败');
} else {
console.warn(new Date(), '执行成功');
}
});
创建同步的进程
const execSync = require('child_process').execSync;
execSync("npm i");
到这里的话基本就已经可以自己完成一个脚手架了,可以怎么共享出去呢?npm install 来了。
5.发布到npm registry
npm registry 可以理解成一个包注册管理中心。它管理着全世界的开发者们发布上来的各种插件,同时开发者们可以通过npm install的方式安装所需要的插件。
npm官方registry:http://registry.npmjs.org/
淘宝cnpm:https://registry.npm.taobao.org/
- 进入要发布到npm的项目根目录,初始化为npm包:
npm init
// 依次按提示填入包名、版本、描述、github地址、关键字、license等
完成之后会生成一个package.json文件,熟悉的东西来了吧,这个就不介绍了。其实这一步应该是开发脚手架项目的第一步,因为我们基本离不开第三方依赖,并且必须有package.json才能安装依赖。
- 第一次发布包在需要先登录,执行
npm login
// 依次输入自己注册的npm账号、密码,可能需要输入邮箱
- 登录后就可以发布了
npm publish
// 如果报错可能是包名已经存在
出现 ’+’ 包名 就表示发布成功了
- 正常更新一个已经发布的包,版本号+1的情况
先执行
npm version patch
// 该命令在原来的版本上自动加1,实际上是将package.json文件中的version值修改了。
再执行 npm publish, 需要build的项目还有build一下
- 更新已经存在的版本,版本号不需要+1的情况
npm unpublish @fx/report@0.5.16
// 先删除版本 再npm publish
- 删除某个版本,命令同上
- 删除整个包
npm unpublish 包名 --force
注意:
1、如果发布时报错:‘no_perms Private mode enable, only admin can publish this module:’
表示当前不是原始镜像,可能用的是其他镜像,如淘宝镜像,私有镜像
要切换回原始的npm镜像,命令:npm config set registry https://registry.npmjs.org/,如果用了nrm工具,使用命令:nrm use npm 切换
2、有些项目需要build后才发布的,直接执行 npm run build可能会报
‘rm’ 不是内部或外部命令,也不是可运行的程序
或批处理文件。
错误原因:是使用了Linux 下的 rm -rf 命令,换成在git bash执行就可以了。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)