自定义脚手架

前言

继上一次搞了一个基于webpack4+react的多页面移动端应用程序,我发现下一次需要使用这个模板文件的时候比较麻烦,首先需要去github下载项目模板下来,然后改项目名称,描述,作者等等这些信息,而且这个模板的css预处理器是node-sass,万一我想用less,就要删掉node-sass,sass-loader,然后添加less,less-loader。这些操作是在有点麻烦,所以我就想着模仿create-react-app和vue-cli这些脚手架,通过命令行快速生成一个符合我要求的项目模板和环境。

需求分析

首先分析一下自定义脚手架需要做些什么事请吧。我先把自己做的这个自定义脚手架命名为lin-cli吧,然后命令就叫做 lin

  • 这个脚手架由于是自定义的,不像react和vue那些脚手架那样有这么多功能,满足需求即可,所以指令那些并不多,只需要3个;分别如下:

    • lin create projectName 类似于vue create,该指令是在当前打开黑窗口的目录下创建一个名为[projectName]的文件夹,并把项目模板那些生成放在里面。
    • lin -v | --version 查看当前脚手架的版本号
    • lin --help 查看脚手架有什么指令
  • 脚手架可以跟用户进行交互,询问项目名称,描述,作者等等信息,并将这些信息填写进package.json文件中

  • 脚手架可以让用户选择是否使用css预处理器。如果选择是,则提供less和sass这2个选项给用户选择

  • 脚手架在生成项目模板的时候,需要给出适应的提示,比如下载模板的时候就提示正在下载模板,下载完成就提示下载完成

模板文件–webpack配置修改

之前webpack配置是基于node-sass配置的,但是现在需要把它变成通用的,所以这里需要把less也配置上去,修改如下

  • 在build/webpack.config.dev.js文件rules下添加以下代码
{
    test: /\.less$/,
    use: [
        'style-loader',
        {
            loader: 'css-loader',
            options: {
                importLoaders: 2
            }
        },
        'postcss-loader',
        'less-loader'
    ],
    include: srcRoot
}
  • 在build/webpack.config.build.js文件rules下添加以下代码
{
    test: /\.less$/,
    use: [
        MiniCssExtractPlugin.loader,
        {
            loader: 'css-loader',
            options: {
                importLoaders: 2
            }
        },
        'postcss-loader',
        'less-loader'
    ],
    include: srcRoot
}

模板文件–package.json修改

这里需要说明的是模板文件的package.json有些字段是需要根据用户的输入去进行填充的,所以要填充的地方要用占位符,到时候利用脚手架替换掉这些占位符,生成一份新的package.json并回写回去。这里使用handlebars这个工具来处理模板的,所以占位符使用的是handlebars的格式规范

{
  "name": "{{name}}",
  "version": "1.0.0",
  "description": "{{description}}",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.config.dev.js",
    "build": "webpack --config ./build/webpack.config.build.js"
  },
  "keywords": [],
  "author": "{{author}}",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.8.7",
    "@babel/plugin-transform-runtime": "^7.8.3",
    "@babel/preset-env": "^7.8.7",
    "@babel/preset-react": "^7.8.3",
    "autoprefixer": "^9.7.4",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^5.1.1",
    "css-loader": "^3.4.2",
    "file-loader": "^5.1.0",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.9.0",
    "postcss-loader": "^3.0.0",
    "react-hot-loader": "^4.12.19",
    {{#if sass}}
    "node-sass": "^4.13.1",
    "sass-loader": "^8.0.2",
    {{/if}}
    {{#if less}}
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    {{/if}}
    "style-loader": "^1.1.3",
    "url-loader": "^3.0.0",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3",
    "webpack-merge": "^4.2.2"
  },
  "dependencies": {
    "@babel/runtime-corejs3": "^7.8.7",
    "axios": "^0.19.2",
    "core-js": "^3.6.4",
    "react": "^16.13.0",
    "react-dom": "^16.13.0",
    "react-loadable": "^5.5.0",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.1.2",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0",
    "regenerator-runtime": "^0.13.3"
  }
}

这里可以看见,需要根据用户的输入填充的字段有name、description、author,以及依赖包node-sass,sass-loader,less,less-loader。这里根据用户的输入选择性把less和sass的依赖填写上去,而不是直接把less和node-sass的依赖全部填写上去,因为这样会产生无用的依赖包,下载的时候会花费更多的时间。

脚手架–package.json配置

package.json文件有几个字段需要注意。

  • name:该字段是用来发布npm包使用的,说明了npm包的名称,也就是publish后可以在npmjs中通过该名称搜索到。
  • version:该字段说明版本号
  • main:该字段是指程序的主入口。
  • bin:该字段是指定你的脚手架在命令行运行的指令,我这里的bin字段配置成"lin": “index.js”,意思是我在命令行中可以使用lin这个指令来调用我得自定义脚手架,自定义脚手架执行的脚本文件就是index.js。比如:lin create testdemo,就是执行index.js文件,并把create testdemo这2个参数传递给index.js。
{
  "name": "lin-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bin": {
    "lin": "index.js"
  },
  "dependencies": {
    "chalk": "^2.4.2",
    "commander": "^2.20.0",
    "download-git-repo": "^2.0.0",
    "handlebars": "^4.1.2",
    "inquirer": "^6.5.0",
    "ora": "^3.4.0"
  }
}

脚手架–index.js脚本文件

生成模板文件总的思路就是

  • 预先设定好一些问题
  • 当用户输入lin create testdemo 并敲下回车键的时候显示预先设定好的问题
  • 当用户输入回答并敲下回车键的时候保存用户的输入
  • 根据用户的输入显示下一个需要显示的问题
  • 当用户回答完预设的问题后,下载模板文件
  • 根据用户输入的信息去修改模板文件
  • 以上步骤完成后提示用户模板文件生成完成
    这里我就直接上代码,然后在代码里面解释,这样更加直观方便
#!/usr/bin/env node
// 使用node开发命令行工具所执行的js脚本必须在顶部加入 #!/usr/bin/env node 生命
// #!/usr/bin/env node  告诉系统该脚本使用node运行,用户必须在系统变量中配置了node

const program = require('commander');

// 下载github仓库
const download = require('download-git-repo');

// 命令行交互
const inquirer = require('inquirer');

// 处理模板
const handlebars = require('handlebars');

// loading效果
const ora = require('ora');

// 给字体增加颜色
const chalk = require('chalk');

const fs = require('fs');

// 模板下载地址
const downloadUrl = 'direct:https://github.com/c10342/react-multipage-template.git';

// 设置脚手架版本
program
    .version('0.1.1', '-v, --version');

// 跟用户进行交互
program
	// lin create testdemo
    .command('create <project>')
    // 添加描述
    .description('初始化项目模板')
    .action(function (projectName) {
        // 与用户交互
        inquirer
            .prompt([
            	// 输入项目名称
                {
                    type: 'input',
                    name: 'name',
                    message: '请输入项目名称:'
                },
                // 输入项目描述
                {
                    type: 'input',
                    name: 'description',
                    message: '请输入项目描述:'
                },
                // 输入项目作者
                {
                    type: 'input',
                    name: 'author',
                    message: '请输入项目作者:'
                },
                // 选择是否使用node-sass或者less
                {
                    type: 'confirm',
                    name: 'cssStyle',
                    message: '是否使用less/node-sass:'
                },
                // 选择使用node-sass或者less
                {
                    type: 'list',
                    message: '请选择以下css预处理器:',
                    name: 'preprocessor',
                    choices: [
                        "less",
                        "sass"
                    ],
                    // 只有当用户在选择是否使用node-sass或者less时输入了yes才会显示该问题
                    when: function (answers) {
                        return answers.cssStyle
                    }
                }
            ])
            .then(answers => {
            	// 与用户交互完成后,处理用户的选择
            	
                let params = {
                    name: answers.name, // 项目名称
                    description: answers.description,// 项目描述
                    author: answers.author // 项目作者
                }
                if (answers.cssStyle) {
                	//用户选择使用css预处理器
                	
                    if (answers.preprocessor === 'less') {
                    	// 用户选择使用less
                        params.less = true;
                        params.sass = false;
                    } else if (answers.preprocessor === 'sass') {
                   		// 用户选择使用sass
                        params.less = false;
                        params.sass = true;
                    }
                } else {
                	// 用户选择不使用css预处理器
                    params.sass = false;
                    params.less = false
                }
                // 打印空行,使输出与输出之间有空行,增加体验效果
                console.log("");
                
                // 提示用户正在下载模板,并显示loading图标
                var spinner = ora('正在下载中...').start();
                
				// 下载模板到本地
                download(downloadUrl, projectName, { clone: true }, err => {
                    if (err) {
                        console.log(err);
                        spinner.text = '下载失败';
                        spinner.fail()  //下载失败
                    } else {
                    	// 获取模板的package.json的路径
                        let packagePath = `${projectName}/package.json`;
                        // 读取模板的package.json文件的内容
                        let packageStr = fs.readFileSync(packagePath, 'utf-8');
                        // 根据params参数替换掉模板的package.json文件内容的占位符
                        let package = handlebars.compile(packageStr)(params);
                        // 重新写入文件
                        fs.writeFileSync(packagePath, package);
                        if (params.sass) {
                        	// 由于国内网络原因,node-sass可能需要翻墙才能下载,所以如果用户选择了sass预处理器则需要创建.npmrc文件,并写入node-sass的代理下载地址
                            const npmrcPath = `${projectName}/.npmrc`;
                            const appendContent = '\r\nsass_binary_site=https://npm.taobao.org/mirrors/node-sass/'
                            if (!fs.existsSync(npmrcPath)) {
                                fs.writeFileSync(npmrcPath, appendContent)
                            } else {
                                fs.appendFileSync(npmrcPath, appendContent)
                            }
                        }
                        // 提示用户下载成功
                        spinner.text = '下载成功';
                        spinner.color = '#13A10E';
                        spinner.succeed();
                        console.log("");
                        // 提示进入下载的目录
                        console.log(" # cd into Project");
                        console.log(chalk.gray('   $ ') + chalk.blue(`cd ${projectName}`));
                        console.log("");
                        // 提示安装依赖
                        console.log(" # Project setup");
                        console.log(chalk.gray('   $ ') + chalk.blue(`npm install`));
                        console.log("");
                        // 提示运行开发环境
                        console.log(" # Compiles and hot-reloads for development");
                        console.log(chalk.gray('   $ ') + chalk.blue(`npm run dev`));
                        console.log("");
                        // 提示打包生产环境代码
                        console.log(" # Compiles and minifies for production");
                        console.log(chalk.gray('   $ ') + chalk.blue(`npm run build`));
                        console.log("")
                    }
                })
            });
    });

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

发布npm包

  • 注册一个npm账号,如果没有可以到 https://www.npmjs.com 注册。
  • 检查你的镜像源,命令行输入 npm config get registry。如果是淘宝镜像源需要切换回npm的镜像源,否则会导致登陆失败报错。
  • 切换镜像源,命令行输入 npm config set registry https://registry.npmjs.org/。如果镜像源已经是npm地址,则跳过这一步。
  • 登录账号,命令行输入 npm login。然后根据提示填写你的用户名,密码,邮箱
  • 发布,在命令行中输入 npm publish。
  • 使用,成功发布后可以再npm上面搜索并安装使用。

效果图

最后再附上几张效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

其实,自定义脚手架还是挺好实现的,总的思路就是预设问题,收集用户的输入,然后下载模板,最后根据用户的输入,修改模板内容。难点并不多,就是需要注意一些小细节,还有就是commander、inquirer、handlebars这些依赖包的用法。

附上地址

模板文件源码地址:https://github.com/c10342/react-multipage-template
脚手架源码地址:https://github.com/c10342/lin-cli
脚手架npm地址:https://www.npmjs.com/package/lin-cli

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值