开发Node CLI构建微信小程序脚手架的示例

 更新时间:2020年03月27日 14:38:34   作者:夏天Summer  
这篇文章主要介绍了开发Node CLI构建微信小程序脚手架,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文介绍了 Node CLI 构建微信小程序脚手架的示例,分享给大家,具体如下:

 

 

目的

由于目前公司的 TOC 产品只要是微信小程序,而且随着业务的扩展, 会有更多的需求,创建更多的小程序,为了让团队避免每次开发前花费大量时间做比如工程化的一些配置,以及保持每个项目的一致性, 所以决定做一个 Node CLI 来创建微信小程序脚手架

  • 节省开发前期的大量时间,新项目可以很快开始业务开发
  • 保证项目统一性,有利于团队间的协作及工程化
  • 提升团队基建意识,从枯燥无味的业务开发中脱离出来,尝试新的东西,即使很基础很简单

小程序选型

小程序的第三方框架有很多, 我接触过的就有 taro / wepy / mpvue ,并且都有对应上线的项目。 在尝试这些框架的过程中,对比原生小程序,有一些感想想分享出来:

  • 第三方框架语法贴近vue/react, 开发者可以根据自己的特点选择框架,学习成本相对较低
  • 原生框架在CSS预处理,多端复用,状态管理,自动构建这几块能力对比其他框架是欠缺的
  • 第三方框架额外的工具包会使打包体积变大,每次构建花费时间,同时性能不如原生
  • 第三方框架更新迭代很快,比如wepy@1.x/wepy@2, 导致旧项目的更新问题
  • 小程序的特性更新迭代速度较快, 第三方框架会相对滞后

综上所述,由于我们目前没有多端复用的要求,并且有的小程序相对简单,需要很短时间内开发完成, 最重要的是,其他的框架我都试过了,原生的还没写过,一个字,新鲜感!!:smile: ,所以最终当仁不让地选择了原生小程序,不得不说,原生大法就是妙啊! :clap::clap::clap::clap:

大体思路

这个功能是相对很基础的,但是作为一个每天搬砖的业务仔来说,是个艰难的过程,也是个很好的学习机会。

在做之前,想找找个社区比较:ox::beer:的学习(抄)一下,短暂考虑后,果断选择 taro-cli , 然后火速打开源码,一顿操作(完全蒙圈),学习了一点之后,才开始上手

这个具体的实现思路我想到两个

  • git clone 远程仓库作为模版下载到本地,再根据用户输入配置修改 .json 文件(比如 appId )
  • template 就放在当前目录中,直接`copy``, 之后的事等同

权衡之后,打算使用 lerna 作为管理工具, 其中模版也作为一个 npm 包 ,用到的时候去 npm 下载,这么做我是为了方便管理,统一 push / publish , 就是为了省事 :smile:。

最终思路:

暴露命令 —> 用户交互输入配置 -> 集合配置下载模版 -> 根据配置修改 .json -> git init + 安装依赖

开发 Node CLI

Lerna 项目搭建

知道 monorepo 的同学不需要我多说,其实就是把代码放在一个仓库里,结果包之间回想以来,发布繁琐等问题, 这里我们就用到了 lerna 这个神器帮助我们做包的统一管理

// 创建项目
mkdir modoo-mini-program
cd modoo-mini-program

// 初始化
lerna init

cd packages
mkdir modoo-script
mkdir modoo-template-mini
mkdir modoo-mini // 安装 modoo-script 依赖用于测试,无其他实际用处

lerna bootstrap // 安装依赖 + npm link

安装依赖

为了实现功能,我们需要安装一些依赖包

  • commander 命令行工具,用于读取命令参数,作对应操作
  • node-fs-extra 在 Node.js 的 fs 基础上增加了一些新的方法,更好用,还可以拷贝模板。
  • chalk 可以用于控制终端输出字符串的样式, 调整颜色啥的
  • inquirer 用户命令行交互,获取用户的交互配置数据,就像个提问板
  • ora 实现加载中的状态是一个 Loading 加前面转起来的小圈圈,成功了是一个 Success 加前面一个小钩钩。
  • log-symbols 日志彩色符号,用来显示√ 或 × 等的图标

获取命令

首先第一步,要在用户全局安装之后,暴露出命令接口,需要在 packages.json 文件中加入如下内容

"bin": {
  "modoo-script": "./bin/modoo-script.js"
},

之后在根目录下创建 bin 文件夹 + bin/modoo-script.js

#!/usr/bin/env node
const { program } = require("commander");

program
 .version(require("../package").version) // modoo-script --version
 .usage("<command> [options]")
 // init 命令,床架项目
 .command("init [projectName]", "Init a project with default templete")
 .parse(process.argv); // 解析命令参数

然后需要注意的是, commander 支持 Git 风格的子命令处理,可以根据子命令自动引导到以特定格式命名的命令执行文件,文件名的格式是 [command]-[subcommand] ,例如:

modoo-script init => modoo-script-init
modoo-script build => modoo-script-build

所以为了实现 init 命令,可以直接在 bin 文件目录下添加 modoo-script-init.js

#!/usr/bin/env node

const { program } = require("commander");

program
 .option("--name [name]", "项目名称")
 .option("--description [description]", "项目介绍")
 .option("--framework", "脚手架框架")
 .parse(process.argv);

const args = program.args;
// 获取命令参数
const { name, description, framework } = program;

const projectName = args[0] || name;

......

用户交互

获取了命令参数后,根据参数转到用户交互界面,这里使用的是 inquirer 来处理命令行交互, 用法很简单

const inquirer = require('inquirer')

if (typeof conf.description !== 'string') {
   prompts.push({
    type: 'input',
    name: 'description',
    message: '请输入项目介绍!'
   })
}

......

inquirer.prompt(prompts).then(answers => {
  // 整合配置
  this.conf = Object.assign(this.conf, answers);
})

远程模块

这里较为折腾,一开始说了,我把模版作为 npm包 ,具体查找,下载的过程如下

  • npm search 查找相应的模版 npm 包
  • 在用户选择框架后对应所需的包,获取它的详细信息,主要是 tarball
  • 用户输入完后,下载 tarball 到项目目录,并修改 .json 文件配置

部分代码如图所示

// 一 npm search 查找相应的模版 npm 包
const { execSync } = require("child_process");

module.exports = () => {
 let list = [];
 try {
  const listJSON = execSync(
   "npm search --json --registry http://registry.npmjs.org/ @modoo/modoo-template"
  );
  list = JSON.parse(listJSON);
 } catch (error) {}

 return Promise.resolve(list);
};
// 二 返回 npm 数据
const pkg = require("package-json");
const chalk = require("chalk");
const logSymbols = require("log-symbols");

exports.getBoilerplateMeta = framework => {
 log(
  logSymbols.info,
  chalk.cyan(`您已选择 ${framework} 远程模版, 正在查询该模版...`)
 );

 return pkg(framework, {
  fullMetadata: true
 }).then(metadata => {
  const {
   dist: { tarball },
   version,
   name,
   keywords
  } = metadata;
  log(
   logSymbols.success,
   chalk.green(`已为您找到 ${framework} 远程模版, 请输入配置信息`)
  );

  return {
   tarball,
   version,
   keywords,
   name
  };
 });
};
// 三 下载 npm 包
const got = require("got");
const tar = require("tar");
const ora = require("ora");

const spinner = ora(
  chalk.cyan(`正在下载 ${framework} 远程模板仓库...`)
).start();

const stream = await got.stream(tarball);

fs.mkdirSync(proPath);

const tarOpts = {
 strip: 1,
 C: proPath
};

// 管道流传输下载文件到当前目录
stream.pipe(tar.x(tarOpts)).on("close", () => {
  spinner.succeed(chalk.green("下载远程模块完成!"));
  ......
})
// 四 遍历文件修改配置
const fs = require("fs-extra");

readFiles(
  proPath,
  {
   ignore: [
    ".{pandora,git,idea,vscode,DS_Store}/**/*",
    "{scripts,dist,node_modules}/**/*",
    "**/*.{png,jpg,jpeg,gif,bmp,webp}"
   ],
   gitignore: true
  },
  ({ path, content }) => {
   fs.createWriteStream(path).end(template(content, inject));
  }
 );
 
// 递归读文件
exports.readFiles = (dir, options, done) => {
 if (!fs.existsSync(dir)) {
  throw new Error(`The file ${dir} does not exist.`);
 }
 if (typeof options === "function") {
  done = options;
  options = {};
 }
 options = Object.assign(
  {},
  {
   cwd: dir,
   dot: true,
   absolute: true,
   onlyFiles: true
  },
  options
 );

 const files = globby.sync("**/**", options);
 files.forEach(file => {
  done({
   path: file,
   content: fs.readFileSync(file, { encoding: "utf8" })
  });
 });
};

// 配置替换
exports.template = (content = "", inject) => {
 return content.replace(/@{([^}]+)}/gi, (m, key) => {
  return inject[key.trim()];
 });
};

下载依赖

下载完毕并且修改完配置后, 默认执行 git init + 根据环境( yarn / npm / cnpm )安装依赖,这个就很简单了

const { exec } = require("child_process");
const ora = require("ora");
const chalk = require("chalk");

// proPath 项目目录
process.chdir(proPath);

// git init
const gitInitSpinner = ora(
 `cd ${chalk.cyan.bold(projectName)}, 执行 ${chalk.cyan.bold("git init")}`
).start();

const gitInit = exec("git init");
gitInit.on("close", code => {
 if (code === 0) {
  gitInitSpinner.color = "green";
  gitInitSpinner.succeed(gitInit.stdout.read());
 } else {
  gitInitSpinner.color = "red";
  gitInitSpinner.fail(gitInit.stderr.read());
 }
});

// install
let command = "";
if (shouldUseYarn()) {
 command = "yarn";
} else if (shouldUseCnpm()) {
 command = "cnpm install";
} else {
 command = "npm install";
}

log(" ".padEnd(2, "\n"));
const installSpinner = ora(
 `执行安装项目依赖 ${chalk.cyan.bold(command)}, 需要一会儿...`
).start();

exec(command, (error, stdout, stderr) => {
  if (error) {
   installSpinner.color = "red";
   installSpinner.fail(chalk.red("安装项目依赖失败,请自行重新安装!"));
   console.log(error);
  } else {
   installSpinner.color = "green";
   installSpinner.succeed("安装成功");
   log(`${stderr}${stdout}`);
  }
});

主要的代码就是这些,其实只要知道思路,这些东西都很简单,虽然我写的有点 ️:chicken:,但是主要的逻辑还是能理清楚的一些的。更加详细的可以去:eyes:我发的源码,多谢指教。:pray::pray::pray:

开发脚手架

因为这是小程序的脚手架,它不像其他 web 框架一样需要很多 webpack 的配置,所以相对简单很多。

对于这个脚手架,相比于开发者工具创建的默认项目,我弥补了它的一些问题

  1. 默认项目太过简单,只适合自己折腾,对于团队或者企业,缺乏相应的代码约定/规范,没有强制的约定会导致团队协作间的困难,提升code review的难度,所以我在原来的基础上加入了eslint,stylelint,prettier,commitlint等配置,以及git hook 在 pre-commit 时,执行校验,确保提交的代码尽量规范
  2. 由于对 css 预处理的钟爱,另外加入了对 less 的支持,并且解决小程序背景图不支持本地图片的问题
  3. 由于以上基本都是文件处理,所以选择 gulp 作为构建工具,这里是 v4, 与v3 写法上有一定的区别,不过关系不大

在根目录下创建 gulpfile.js

const gulp = require('gulp');
const chalk = require('chalk');
const rename = require('gulp-rename');

// 支持 less
gulp.task('less', () => {
 return gulp
  .src('./miniprogram/**/*.less')
  .pipe(less())
  .pipe(postcss()) // 配置在 post.config.js
  .pipe(
   rename((path) => {
    path.extname = '.wxss';
   })
  )
  .pipe(
   gulp.dest((file) => {
    return file.base; // 原目录
   })
  );
});

// 开发环境监听 less
if (env === 'development') {
 gulp.watch(['./miniprogram/**/*.less'], gulp.series('less')).on('change', (path) => {
  log(chalk.greenBright(`File ${path} was changed`));
 });
}


// 一下代码注释掉了,依赖包下载太慢了,这主要负责图片的压缩
const imagemin = require('gulp-imagemin');
const cache = require('gulp-cache'); // 使用缓存

gulp.task('miniimage', () => {
 return gulp
  .src('./miniprogram/**/*.{png,jpe?g,gif,svg}')
  .pipe(
   cache(
    imagemin([
     imagemin.gifsicle({ interlaced: true }),
     imagemin.mozjpeg({ quality: 75, progressive: true }),
     imagemin.optipng({ optimizationLevel: 5 }),
     imagemin.svgo({
      plugins: [{ removeViewBox: true }, { cleanupIDs: false }],
     }),
    ])
   )
  )
  .pipe(
   gulp.dest((file) => {
    return file.base; // 原目录
   })
  );
});

其他的一些具体配置,可以看我的GitHub 仓库源码

参考

taro-cli

pandora-cli

little-bird-cli

到此这篇关于开发Node CLI构建微信小程序脚手架的示例的文章就介绍到这了,更多相关Node CLI构建小程序脚手架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Nest.js系列之Providers及模块功能使用详解

    Nest.js系列之Providers及模块功能使用详解

    这篇文章主要为大家介绍了Nest.js系列之Providers及模块功能使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • nodejs项目windows下开机自启动的方法

    nodejs项目windows下开机自启动的方法

    今天小编就为大家分享一篇nodejs项目windows下开机自启动的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-11-11
  • Nodejs如何进行性能监控和分析优化

    Nodejs如何进行性能监控和分析优化

    Node.js应用可能因为高并发、内存泄漏、CPU密集型任务等原因导致性能下降,影响用户体验甚至系统稳定性,通过性能监控和分析,我们可以及时发现潜在问题,并针对性地进行优化,确保系统正常运行且具备良好的性能表现
    2024-06-06
  • 深入解析koa之异步回调处理

    深入解析koa之异步回调处理

    这篇文章主要介绍了深入解析koa之异步回调处理,我们研究一下koa当中异步回调同步化写法的原理,同样的,我们也会实现一个管理函数,是的我们能够通过同步化的写法来写异步回调函数。,需要的朋友可以参考下
    2019-06-06
  • 关于node.js版本npm -v报错问题的解决方法

    关于node.js版本npm -v报错问题的解决方法

    最近工作中遇到了些问题,这里总结下,下面这篇文章主要给大家介绍了关于node.js版本npm -v报错问题的解决方法,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • node.js操作mongodb简单示例分享

    node.js操作mongodb简单示例分享

    MongoDB是基于Javascript语言的数据库,存储格式是JSON,而Node也是基于JavaScript的环境(库),所以node和mongoDB的搭配能减少因为数据转换带来的时间空间开销。今天我们来看看如何通过node.js来操作MongoliaDB
    2017-05-05
  • 如何构建一个 NodeJS 影院微服务并使用 Docker 部署

    如何构建一个 NodeJS 影院微服务并使用 Docker 部署

    微服务是一个单独的自包含单元,与其他许多单元一起构成一个大型应用程序,这篇文章主要介绍了如何构建一个NodeJS影院微服务并使用Docker部署,在这个系列中,将构建一个 NodeJS 微服务,并使用 Docker Swarm 集群进行部署,需要的朋友可以参考下
    2023-08-08
  • 详解Nodejs基于mongoose模块的增删改查的操作

    详解Nodejs基于mongoose模块的增删改查的操作

    本篇文章主要介绍了Nodejs基于mongoose模块的增删改查的操作,Mongoose是MongoDB的一个对象模型工具,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单。
    2016-12-12
  • node实现简单的反向代理服务器

    node实现简单的反向代理服务器

    本篇文章主要介绍了node实现简单的反向代理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • 一文详解Node中的文件模块与核心模块

    一文详解Node中的文件模块与核心模块

    这篇文章主要介绍了一文详解Node 中文件模块与核心模块,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-07-07

最新评论