Node.js系列七 - coderwhy脚手架开发

简介: 目前前端工程化开发过程中,我们会使用各种各样的脚手架,vue-cli、create-react-app,当然也包括webpack、gulp、rollup、vite等工具。这些工具是怎么开发出来的呢?当我们执行一个命令时,它们做了什么事情?是怎么样完成的一系列操作?这里我开发了一个coderwhy的脚手架:一个帮助你快速搭建和开发前端项目的CLI。文档内容分成两部分:第一部分:coderwhy使用说明;第二部分:coderwhy脚手架开发过程;欢迎下载学习,如果对你有帮助,可以点一个star~脚手架地址:https://github.com/coderwhy/code

说明文档


coderwhy: 一个帮助你快速搭建和开发前端项目的CLI

想不起来其他名字,以这个命名吧~

如何安装?

npm install coderwhy -g


一. 创建项目


目前支持Vue,后期会支持React,Angular考虑中~

vue项目模块已经帮你配置:

  • 常用的目录结构(你可以在此基础上修改)
  • vue.config.js(其中配置了别名,你可以自行修改和配置更多)
  • axios(网络请求axios的安装以及二次封装)
  • vue-router(router的安装和配置,另外有路由的动态加载,后面详细说明)
  • vuex(vuex的安装和配置,另外有动态加载子模块,后面详细说明)

创建项目

coderwhy create your_project_name

自动拉取项目模板、安装项目依赖、打开浏览器 http://localhost:8080/、自动启动项目


二. 项目开发



项目开发目前提供三个功能:

  • 创建Vue组件
  • 创建Vue页面,并配置路由
  • 创建Vuex子模块


2.1. 创建Vue组件:

coderwhy addcpn YourComponentName # 例如coderwhy add NavBar,默认会存放到src/components文件夹中
coderwhy addcpn YourComponentName -d src/pages/home # 也可以指定存放的具体文件夹


2.2. 创建Vue页面,并配置路由


coderwhy addpage YourPageName # 例如coderwhy addpage Home,默认会放到src/pages/home/Home.vue中,并且会创建src/page/home/router.js
coderwhy addpage YourPageName -d src/views # 也可以指定文件夹,但需要手动集成路由

为什么会创建router.js文件:

  • router.js文件是路由的其中一个配置;
  • 创建该文件中 src/router/index.js中会自动加载到路由的 routes配置中,不需要手动配置了(如果是自己配置的文件夹需要手动配置)

src/router/index.js中已经完成如下操作:

// 动态加载pages中所有的路由文件
const files = require.context('@/pages', true, /router\.js$/);
const routes = files.keys().map(key => {
  const page = require('@/pages' + key.replace('.', ''));
  return page.default;
})


2.3. 创建Vuex子模块


coderwhy addstore YourVuexChildModuleName # 例如coderwhy addstore home,默认会放到src/store/modules/home/index.js和types.js
coderwhy addstore YourVuexChildModuleName -d src/vuex/modules # 也可以指定文件夹

创建完成后,不需要手动配置,已经动态将所有子模块集成进去:

// 动态加载modules
const modules = {}
const files = require.context('./', true, /index\.js$/);
files.keys().filter(key => {
  if (key === './index.js') return false;
  return true
}).map(key => {  
  // 获取名字
  const modulePath = key.replace('./modules/', '');
  const moduleName = modulePath.replace('/index.js', '');
  const module = require(`${key}`);
  modules[`${moduleName}`] = module.default;
})


开发过程


一. 项目开始


创建index.js

console.log("Hello Coderwhy")

创建package.json

{
  "name": "coderwhy",
  "version": "1.1.0",
  "description": "CLI front-end development tools",
  "main": "index.js",
  "bin": {
    "coderwhy": "index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "vue",
    "react",
    "CLI",
    "component"
  ],
  "author": "coderwhy",
  "license": "MIT",
  "homepage": "https://github.com/coderwhy/coderwhy",
  "repository": {
    "type": "git",
    "url": "https://github.com/coderwhy/coderwhy"
  },
  "dependencies": {
    "chalk": "^4.1.0",
    "commander": "^6.1.0",
    "download-git-repo": "^3.0.2",
    "ejs": "^3.1.5",
    "open": "^7.3.0"
  }
}

最终的目录结构:

├── LICENSE
├── index.js
├── lib
│   ├── config
│   │   └── repo_config.js
│   ├── core
│   │   ├── actions.js
│   │   ├── create.js
│   │   └── help.js
│   ├── template
│   │   ├── component.vue.ejs
│   │   ├── vue-router.js.ejs
│   │   ├── vue-store.js.ejs
│   │   └── vue-types.js.ejs
│   └── utils
│       ├── file.js
│       ├── log.js
│       └── terminal.js
├── package-lock.json
├── package.json
└── readme.md


二. 创建coderwhy的命令


自动在你的环境变量中查找node

注意:必须放在第一行

#!/usr/bin/env node

修改package.json

"bin": {
    "coderwhy": "index.js"
  }

执行npm link


三. commander用法


3.1. 定义版本号


#!/usr/bin/env node
const cmd = require('commander');
// 定义显示模块的版本号
cmd.version(require('./package.json').version);
// 解析终端指令
cmd.parse(process.argv);


3.2. 给help增加其他选项


添加单个选项

program.option('-s --src <src>', 'a source folder');
program.option('-d --dest <dest>', 'a destination folder');
program.option('-f --framework <framework>', 'your framework name');

监听help指令

program.on('--help', function() {
  console.log("");
  console.log("usage");
  console.log("   coderwhy -v");
  console.log("   coderwhy -version");
})


四. 创建项目指令


// 创建命令
program
  .command('create <project> [otherArgs...]')
  .description('clone a repository into a newly created directory')
  .action((project, otherArgs) => {
    console.log(project);
    console.log(otherArgs);
   // 调用封装的函数
   createProject(project, otherArgs)
  })

在actions中封装创建过程:

const downloadRepo = promisify(require('download-git-repo'));
const createProject = async (project, otherArg) => {
  // 1.提示信息
  console.log('coderwhy helps you create your project, please wait a moment~');
  // 2.clone项目从仓库
  await downloadRepo(repoConfig.vueGitRepo, project, { clone: true });
  // 3.执行终端命令npm install
  // terminal.exec('npm install', {cwd: `./${project}`});
  const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
  await terminal.spawn(npm, ['install'], { cwd: `./${project}` });
  // 4.打开浏览器
  open('http://localhost:8080/');
  // 5.运行项目
  await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });
}

配置的Git地址如下:

  • 后续会开发一个设置自己地址的指令
const vueGitRepo = "direct:https://github.com/coderwhy/hy-vue-temp.git";
module.exports = {
  vueGitRepo
}

封装执行终端命令的过程:

const { spawn, exec } = require('child_process');
const spawnCommand = (...args) => {
  return new Promise((resole, reject) => {
    const childProcess = spawn(...args);
    childProcess.stdout.pipe(process.stdout);
    childProcess.stderr.pipe(process.stderr);
    childProcess.on('close', () => {
      resole();
    });
  })
}


五. 添加组件指令


5.1. 封装ejs模板


组件模块如下:

<%_ if(data) { _%>
<template>
  <div class="<%= data.lowerName %>">
    <h2>{{ message }}</h2>
  </div>
</template>
<script>
  export default {
    name: "<%= data.name %>",
    components: {
    },
    mixins: [],
    props: {
    },
    data: function() {
      return {
        message: "Hello <%= data.name %>"
      }
    },
    created: function() {
    },
    mounted: function() {
    },
    computed: {
    },
    methods: {
    }
  }
</script>
<style scoped>
  .<%= data.lowerName %> {
  }
</style>
<%_ } _%>

路由模板:

  • 组件模板,直接使用上面的即可
  • router.js模板
<%_ if (data) { _%>
// 普通加载路由
// import <%= data.name %> from './<%= data.name %>.vue'
// 懒加载路由
const <%= data.name %> = () => import('./<%= data.name %>.vue')
export default {
  path: '/<%= data.lowerName %>',
  name: '<%= data.name %>',
  component: <%= data.name %>,
  children: [
  ]
}
<%_ } _%>

vuex模块的模板

  • index.js模板
  • types.js模板

index.js模块

import * as types from './types.js'
export default {
  namespaced: true,
  state: {
  },
  mutations: {
  },
  actions: {
  },
  getters: {
  }
}

types.js模块

export {
}


5.2. 封装ejs解析


封装ejs的编译过程:

const ejsCompile = (templatePath, data={}, options = {}) => {
  return new Promise((resolve, reject) => {
    ejs.renderFile(templatePath, {data}, options, (err, str) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(str);
    })
  })
}

封装创建文件夹的过程:

const mkdirSync = (dirname) => {
  if (fs.existsSync(dirname)) {
    return true
  } else {
    // 不存在,判断父亲文件夹是否存在?
    if (mkdirSync(path.dirname(dirname))) {
      // 存在父亲文件,就直接新建该文件
      fs.mkdirSync(dirname)
      return true
    }
  }
}

封装写入文件的过程:

const writeFile = (path, content) => {
  if (fs.existsSync(path)) {
    log.error("the file already exists~")
    return;
  }
  return fs.promises.writeFile(path, content);
}

封装ejs到文件的转化过程:

const handleEjsToFile = async (name, dest, template, filename) => {
  // 1.获取模块引擎的路径
  const templatePath = path.resolve(__dirname, template);
  const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
  // 2.写入文件中
  // 判断文件不存在,那么就创建文件
  mkdirSync(dest);
  const targetPath = path.resolve(dest, filename);
  writeFile(targetPath, result);
}


5.3. 创建添加指令


添加指令

program
  .command('addcpn <name>')
  .description('add vue component, 例如: coderwhy addcpn NavBar [-d src/components]')
  .action(name => addComponent(name, program.dest || 'src/components'))
program
  .command('addpage <name>')
  .description('add vue page, 例如: coderwhy addpage Home [-d dest]')
  .action(name => {
  addPage(name, program.dest || `src/pages/${name.toLowerCase()}`)
})
program
  .command('addstore <name>')
  .description('add vue store, 例如: coderwhy addstore favor [-d dest]')
  .action(name => {
  addStore(name, program.dest || `src/store/modules/${name.toLowerCase()}`)
})

封装对应的action

const addComponent = async (name, dest) => {
  handleEjsToFile(name, dest, '../template/component.vue.ejs', `${name}.vue`);
}
const addPage = async (name, dest) => {
  addComponent(name, dest);
  handleEjsToFile(name, dest, '../template/vue-router.js.ejs', 'router.js')
}
const addStore = async (name, dest) => {
  handleEjsToFile(name, dest, '../template/vue-store.js.ejs', 'index.js')
  handleEjsToFile(name, dest, '../template/vue-types.js.ejs', 'types.js')
}

六. 发布工具

注册npm账号:

image.png                                                         sign up注册

在命令行登录:

npm login
# 输入账号、密码、邮箱

修改好package.json文件:

"keywords": [
    "vue",
    "react",
    "CLI",
    "component"
  ],
  "author": "coderwhy",
  "license": "MIT",
  "homepage": "https://github.com/coderwhy/coderwhy",
  "repository": {
    "type": "git",
    "url": "https://github.com/coderwhy/coderwhy"
  },

发布到npm registry中

npm publish

更新registry

# 1.修改版本号(最好符合semver规范)
# 2.重新发布

删除发布的包:

npm unpublish

过期发布的包:

npm deprecate
相关文章
|
2月前
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
55 1
|
1月前
|
Web App开发 JavaScript 前端开发
Node.js开发
Node.js开发
50 13
|
2月前
|
JavaScript 前端开发 开发工具
从零开始使用node.js制作一个脚手架
本文介绍了如何从零开始使用Node.js制作一个项目脚手架,涵盖了脚手架的基本概念、所需准备的第三方库、项目结构的初始化、命令注册、项目创建流程及用户交互设计等内容。通过实例演示了如何利用commander、inquirer等库实现命令行工具的开发,最终完成了一个能够根据用户选择自动创建Vue或React项目的脚手架。
34 1
从零开始使用node.js制作一个脚手架
|
2月前
|
存储 JavaScript 前端开发
深入浅出Node.js后端开发
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将以Node.js为例,深入探讨其背后的哲学思想、核心特性以及在实际项目中的应用,旨在为读者揭示Node.js如何优雅地处理高并发请求,并通过实践案例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和思考。
|
2月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
本文将带你领略Node.js的魅力,从基础概念到实践应用,一步步深入理解并掌握Node.js在后端开发中的运用。我们将通过实例学习如何搭建一个基本的Web服务,探讨Node.js的事件驱动和非阻塞I/O模型,以及如何利用其强大的生态系统进行高效的后端开发。无论你是前端开发者还是后端新手,这篇文章都会为你打开一扇通往全栈开发的大门。
|
2月前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
在这篇文章中,我们将一起探索Node.js的奇妙世界。无论你是刚接触后端开发的新手,还是希望深化理解的老手,这篇文章都适合你。我们将从基础概念开始,逐步深入到实际应用,最后通过一个代码示例来巩固所学知识。让我们一起开启这段旅程吧!
|
1月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带领读者从零基础开始,一步步深入到Node.js后端开发的精髓。我们将通过通俗易懂的语言和实际代码示例,探索Node.js的强大功能及其在现代Web开发中的应用。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的后端开发技能更上一层楼。
|
2月前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
2月前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带你走进Node.js的世界,从基础到进阶,逐步解析Node.js在后端开发中的应用。我们将通过实例来理解Node.js的异步特性、事件驱动模型以及如何利用它处理高并发请求。此外,文章还会介绍如何搭建一个基本的Node.js服务器,并探讨如何利用现代前端框架与Node.js进行交互,实现全栈式开发。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
40 4
|
2月前
|
前端开发 JavaScript 关系型数据库
基于 Vue2.0 + Nest.js 全栈开发的后台应用
Vue2 Admin 是一个基于 Vue2 和 Ant Design Pro 开发的前端项目,配合 Nest.js 构建的后端,提供了一个完整的全栈后台应用解决方案。该项目支持动态国际化、用户权限管理、操作日志记录等功能,适合全栈开发者学习参考。线上预览地址:https://vue2.baiwumm.com/,用户名:Admin,密码:abc123456。