想实现一个page复用的功能

https://blog.csdn.net/luchuanqi67/article/details/91283513

https://www.jianshu.com/p/55addf3cbbbd

通过命令 leah create myproject 创建一个项目

npm init -y 生成package.json

1.在package.json中加入创建指令

在package.json中配置项目入口文件

  1. "bin":{
  2. "leah" : "index.js"
  3. }

index.js是项目入口文件,这个指令告诉node可以通过leah create demo创建一个项目

怎么执行这个入口文件呢?

2.在index.js中加入运行文件的环境

需要在入口文件中加入下面这行命令,表示在node环境中运行这个文件

  1. #!/usr/bin/env node

通过npm link与环境变量进行关联

3.自定义指令

依赖commander这个库

  1. npm install commander

在index.js中引入

  1. const program = require("commander")

3.1版本指令

  1. program.version(require('./package.json').version);

通过package.json获取版本信息

通过 leah —version 可以查看当前包的版本

3.2help指令

通过program.option('--leah', 'a vue cli')这样的方式定义help指令,然后通过leah --help可以查看到加入option的help指令.

我们的目的是可以通过 leah addComponent Home -d src/component 这样的方式创建一个一个组件到指定目录下,所以就需要拿到这个指定的目录地址。

  1. program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/pages, 错误/src/pages');

**<dest>**表示可选参数, 可以通过**program.dest**拿到指定的路径 src/pages

  1. program.option('-w --why', 'a coderwhy option');
  2. program.option('-s --src <src>', 'a source folder');
  3. // <dest> 表示可选参数, 可以通过program.dest 拿到指定的路径
  4. program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/pages, 错误/src/pages');
  5. // 指定选择哪个框架 vue react
  6. program.option('-f --framework <framework>', 'your framework name');
  7. // 监听help指令,可以做一些操作,比如修改指令等
  8. program.on('--help', function() {
  9. console.log("");
  10. console.log("usage");
  11. console.log(" coderwhy -v");
  12. console.log(" coderwhy -version");
  13. })

4.创建项目指令

敲下 leah create demo 发生了什么事

  • 去github仓库拉取我们配置好的项目结构
  • 执行 npm install 安装项目依赖
  • 执行 npm run serve 启动项目
  • 打开浏览器

4.1 git仓库拉取代码download-git-repo

  1. // 创建项目指令
  2. program
  3. .command('create <project> [otherArgs...]') // 其中command 是添加子命令 <project> 是项目名称 [otherArgs...] 其他可选参数 可以通过project拿到项目名称
  4. .description('clone a repository into a newly created directory') // description是描述
  5. .action(createProject); // action 是执行这个命令的回调 可以接收一个promise回调

此命令表示要创建一个 demo的项目,就会去github上拉取我们的项目放到 demo的文件夹里面,这个就是上面指定的action回调需要做的。将这部分回调函数放到单独的一个文件里action.js

action.js

  1. const { promisify } = require('util');
  2. const program = require('commander');
  3. const downloadRepo = promisify(require('download-git-repo'));
  4. const open = require('open');
  5. const log = require('../utils/log');
  6. const terminal = require('../utils/terminal');
  7. const repoConfig = require('../config/repo_config');
  8. const createProject = async (project, otherArg) => {
  9. // 1.提示信息
  10. log.hint('leah-cli helps you create your project, please wait a moment~');
  11. // 2.clone项目从仓库
  12. await downloadRepo(repoConfig.vueGitRepo, project, { clone: true });
  13. // 3.执行终端命令npm install
  14. // terminal.exec('npm install', {cwd: `./${project}`});
  15. const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
  16. // cwd: 子进程的当前工作目录。
  17. await terminal.spawn(npm, ['install'], { cwd: `./${project}` }); //
  18. // 4.打开浏览器
  19. open('http://localhost:8080/');
  20. // 5.运行项目
  21. await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });
  22. }

repo_config.js 存放我们的下载地址

  1. // https的地址要通过direct下载
  2. const vueGitRepo = "direct:https://github.com/coderwhy/hy-vue-temp.git";
  3. module.exports = {
  4. vueGitRepo
  5. }

补充:promisify

promisify是nodejs提供的一个工具函数,可以将一些不支持promise写法的回调函数转换成promise函数,之后我们就可以通过 .then或者async和await来调用。

例如:

  1. require('fs').readFile('./test.js', 'utf-8', function (err, data) {
  2. if (err) {
  3. console.log(err)
  4. }
  5. console.log(data)
  6. })

使用promise改造

  1. function readfile() {
  2. return new Promise(function (resolve, reject) {
  3. require('fs').readFile('./test.js', 'utf-8', function (err, data) {
  4. if (err) {
  5. reject(err)
  6. }
  7. resolve(data)
  8. })
  9. })
  10. }
  11. readfile().then(function (data) {console.log(data)})

promisify的原理

  1. function promisify (f) {
  2. return function () {
  3. let args = Array.prototype.slice.call(arguments) // f函数的参数
  4. console.log(args)
  5. return new Promise( (resolve, reject) => {
  6. args.push(function(err, result) {
  7. console.log(err)
  8. console.log(result)
  9. if(err) reject(err) // 错误原因
  10. else resolve(result) // 输出结果
  11. })
  12. f.apply(null, args) // 执行f函数
  13. })
  14. }
  15. }

4.2 安装项目依赖指令 npm install

如上我们可以通过执行 npm install 安装项目依赖

将这些终端命令封装到一个terminal.js文件

  1. const { spawn, exec } = require('child_process');
  2. const spawnCommand = (...args) => {
  3. // const childProcess = spawn(...args,callback); // 这个函数会创建异步进程
  4. // // 让用户看到这个安装过程
  5. // childProcess.stdout.pipe(process.stdout); //标准输出流 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息。
  6. // childProcess.stderr.pipe(process.stderr); // 标准错误流
  7. // // 执行完,关闭
  8. // childProcess.on('close', () => {
  9. // callback(); // 执行回调
  10. // })
  11. // 将上面改写成promise
  12. return new Promise((resole, reject) => {
  13. const childProcess = spawn(...args); // 这个函数会创建异步进程
  14. // 让用户看到这个安装过程
  15. childProcess.stdout.pipe(process.stdout); //标准输出流 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息。
  16. childProcess.stderr.pipe(process.stderr); // 标准错误流
  17. // 执行完,关闭
  18. childProcess.on('close', () => {
  19. resole(); // 将执行结果返回,可以通过.then调用,也可以通过async await执行回调
  20. })
  21. })
  22. }

为了防止回调地狱问题,我们要将普通函数封装成promise对象,resolve将执行结果抛出,可以通过.then调用,也可以通过async await执行回调 。

child_process

child_process有两种启动方式,一中是spawn,一种是exec,他俩的区别是:

https://www.cnblogs.com/xiaoniuzai/p/6889164.html

https://segmentfault.com/a/1190000002913884

child_process.spawn(command[, args][, options])

  • command 要运行的命令。
  • args 字符串参数的列表。
  • options
    • cwd 子进程的当前工作目录。

我们需要传入这三个参数

  1. await terminal.spawn(npm, ['install'], { cwd: `./${project}` });

4.3 运行项目 npm run serve

  1. // 5.运行项目
  2. await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });

4.4 浏览器打开项目

  1. // 4.打开浏览器
  2. open('http://localhost:8080/'); // open是一个库

也可以在项目的webpack配置中自动打开项目。

inquirer库 可以做出这种流程询问的效果

脚手架工具 - 图1

5.添加组件指令

需要一个组件名称name,创建的文件目录dest

使用ejs创建模板代码

将ejs编译成字符串,

再将这个字符串写入.vue文件,

最后创建好的.vue文件写入到dest目录

5.1 创建ejs模板

  1. <template>
  2. <div class="<%= data.lowerName %>">
  3. <h2>{{ message }}</h2>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name: "<%= data.name %>",
  9. components: {
  10. },
  11. mixins: [],
  12. props: {
  13. },
  14. data: function() {
  15. return {
  16. message: "Hello <%= data.name %>"
  17. }
  18. },
  19. created: function() {
  20. },
  21. mounted: function() {
  22. },
  23. computed: {
  24. },
  25. methods: {
  26. }
  27. }
  28. </script>
  29. <style scoped>
  30. .<%= data.lowerName %> {
  31. }
  32. </style>

我们可以通过传入data对象来创建我们的模板

5.2编译模板

通过ejs.renderFile这个api实现

file.js

  1. const fs = require('fs');
  2. const path = require('path');
  3. const ejs = require('ejs');
  4. const log = require('./log');
  5. const ejsCompile = (templatePath, data={}, options = {}) => {
  6. // ejs.renderFile(templatePath, {data}, options, (err, str) => {
  7. // if (err) {
  8. // return;
  9. // }
  10. // callback(); // 执行回调
  11. // })
  12. // 将上面改写成 promise
  13. return new Promise((resolve, reject) => {
  14. // renderFile 渲染哪个ejs模板文件
  15. ejs.renderFile(templatePath, {data}, options, (err, str) => {
  16. if (err) {
  17. reject(err); // 将错误抛出
  18. return;
  19. }
  20. resolve(str); // 将结果输出
  21. })
  22. })
  23. }
  24. module.exports = {
  25. ejsCompile,
  26. }

action.js

  1. /**
  2. *
  3. * @param {*} name 用户输入的要闯将的组件名称
  4. * @param {*} dest 创建在哪个目录下
  5. * @param {*} template 用哪个模板创建
  6. * @param {*} filename 文件名
  7. */
  8. const handleEjsToFile = async (name, dest, template, filename) => {
  9. // 1.获取模块引擎的路径 path.resolve(__dirname, template)获取当前模块文件所在目录的完整绝对路径
  10. const templatePath = path.resolve(__dirname, template);
  11. // 2. ejsCompile 编译
  12. const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
  13. }
  14. const addComponent = async (name, dest) => {
  15. handleEjsToFile(name, dest, '../template/component.vue.ejs', `${name}.vue`);
  16. }

create.js

  1. program
  2. .command('addcpn <name>')
  3. .description('add vue component, 例如: coderwhy addcpn NavBar [-d src/components]')
  4. .action(name => addComponent(name, program.dest || 'src/components'))

addComponent(name, program.dest || 'src/components')) ——> handleEjsToFile(name, dest, '../template/component.vue.ejs',${name}.vue); ——> const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});

path.resolve(__dirname) 获取当前模块文件所在目录的完整绝对路径,比如:

  1. d:\资料库\学习资料\封装自己的脚手架\coderwhy-main\lib\utils

5.3写入文件

使用fs.promises.writeFile写入

file.js

  1. const writeFile = (path, content) => {
  2. if (fs.existsSync(path)) {
  3. log.error("the file already exists~")
  4. return;
  5. }
  6. return fs.promises.writeFile(path, content);
  7. }

actions.js

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

6.添加page指令

如何看待前端这个行业

最近在关注前端什么技术