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
相关文章
|
30天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
506 1
|
1月前
|
JavaScript 前端开发 安全
TypeScript的优势与实践:提升JavaScript开发效率
【10月更文挑战第8天】TypeScript的优势与实践:提升JavaScript开发效率
|
1月前
|
JavaScript 前端开发 IDE
深入理解TypeScript:提升JavaScript开发的利器
【10月更文挑战第8天】 深入理解TypeScript:提升JavaScript开发的利器
28 0
|
3天前
|
JavaScript 前端开发 测试技术
探索现代JavaScript开发的最佳实践
本文探讨了现代JavaScript开发中的最佳实践,涵盖ES6+特性、现代框架使用、模块化与代码分割、测试驱动开发、代码质量与性能优化、异步编程、SPA与MPA架构选择、服务端渲染和静态站点生成等内容,旨在帮助开发者提升代码质量和开发效率。
|
6天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
【10月更文挑战第36天】本文将引导您探索Node.js的世界,通过实际案例揭示其背后的原理和实践方法。从基础的安装到高级的异步处理,我们将一起构建一个简单的后端服务,并讨论如何优化性能。无论您是新手还是有经验的开发者,这篇文章都将为您提供新的视角和深入的理解。
|
11天前
|
Web App开发 存储 JavaScript
深入浅出Node.js后端开发
【10月更文挑战第31天】本文将引导你进入Node.js的奇妙世界,探索其如何革新后端开发。通过浅显易懂的语言和实际代码示例,我们将一起学习Node.js的核心概念、搭建开发环境,以及实现一个简单但完整的Web应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇通往高效后端开发的大门。
|
8天前
|
运维 监控 JavaScript
鸿蒙next版开发:分析JS Crash(进程崩溃)
在HarmonyOS 5.0中,JS Crash指未处理的JavaScript异常导致应用意外退出。本文详细介绍如何分析JS Crash,包括异常捕获、日志分析和典型案例,帮助开发者定位问题、修复错误,提升应用稳定性。通过DevEco Studio收集日志,结合HiChecker工具,有效解决JS Crash问题。
25 4
|
12天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
【10月更文挑战第30天】本文将通过一个Node.js的简单示例,引导你进入Node.js的世界。我们将从基础概念讲起,然后一步步深入到代码实现,最后总结Node.js在后端开发中的优势和应用场景。无论你是前端开发者还是后端新手,这篇文章都将为你打开一扇了解Node.js的大门。
26 2
|
20天前
|
开发框架 JavaScript 前端开发
HarmonyOS UI开发:掌握ArkUI(包括Java UI和JS UI)进行界面开发
【10月更文挑战第22天】随着科技发展,操作系统呈现多元化趋势。华为推出的HarmonyOS以其全场景、多设备特性备受关注。本文介绍HarmonyOS的UI开发框架ArkUI,探讨Java UI和JS UI两种开发方式。Java UI适合复杂界面开发,性能较高;JS UI适合快速开发简单界面,跨平台性好。掌握ArkUI可高效打造符合用户需求的界面。
72 8
|
19天前
|
JavaScript 前端开发
javascript开发的简单的弹幕插件
这是一个原生javascript开发的简单的弹幕插件,具有美观、易用,占用的资源较低等特点,可以给弹幕设置内容、颜色、头像、链接地址等属性,鼠标悬停等,简单实用,欢迎下载!
35 5