🚀自定义属于你的脚手架并发布到NPM仓库

简介: 🚀自定义属于你的脚手架并发布到NPM仓库

前言

我们在开发的过程中常常会使用到一些脚手架来帮我们快速构建项目模版,常用的脚手架命令有如下这些:

  • create-react-app
  • vue-cli
  • create-vite

我们同样也可以根据自己常用的、习惯的技术栈去自定义一个属于自己的脚手架,让自己用的更舒服。所以我根据自己的开发习惯,实现了一个基于vitereact的脚手架。本文主要实现的功能有:

  • 交互式命令行创建
  • 动态模板生成
  • 发布到npm仓库

GitHub地址:github.com/jayyliang/v…

整体流程

大概看一下整个项目的结构 34.png

  • bin 我们要实现的命令行命令
  • src 具体的代码实现
  • template 模版文件

下面简单介绍一下生成过程的整体流程,主要包括以下几点

  • 获取命令行参数
  • 动态生成文件
  • 代码美化
//bin/create.js
#!/usr/bin/env node
const { PROMPT } = require("../src/constants");
const { copyFolderSync,format } = require("../src/exec");
const { getPrompt } = require("../src/interactive")
const path = require('path');
const run = async () => {
  const prompts = await getPrompt()
  //获取目录
  const projectName = prompts[PROMPT.NAME]
  const projectPath = path.join(process.cwd(), projectName)
  //生成模版
  copyFolderSync(path.join(__dirname, "../template"), projectPath, prompts)
  // 代码美化
  await format(projectPath)
  console.log(`🚀 项目地址:${projectPath}`)
}

run()

交互式命令行

这里主要用到的是inquirer这个库,它十分强大,可以很容易的帮我们创建一个交互式的命令行。比如我们希望创建的时候输入项目名称,选择Javascript/Typescript。就可以如下实现:

const inquirer = require("inquirer");
const prompts = [
  {
    type: "input",
    name: PROMPT.NAME,
    message: "项目名称",
  },
  {
    type: "list",
    name: PROMPT.LANG,
    message: "JS/TS",
    choices: [ENUMS[PROMPT.LANG].JavaScript, ENUMS[PROMPT.LANG].TypeScript],
  },
];

const commandRes = await inquirer.prompt(prompts);

35.png

动态模版生成

在前面的命令行交互过程中,我们已经拿到了用户的各种输入。数据结构如下: 36.png

{
  NAME: 'project-name',
  LANG: 'TypeScript',
  axios: 'y',
  mobx: 'y',
  LIB: [ 'antd', 'lodash', 'dayjs' ]
}

这个时候我们需要一个项目模版,大致的文件目录结构如下 38.png

整个脚手架的创建流程如下

  • 入口文件为/bin/create.js
  • 解析命令行输入
  • 根据输入递归解析模版文件夹,生成模版
  • 代码美化

动态模板生成

接下来就要根据命令行的输入去替换模版的内容,这里可以做一个约定,在模版中的js文件必须实现一个getContentgetExt方法,因为要根据不同的输入去生成不同的内容。而其他文件可以按需直接拷贝到目标目录中。

使用copyFolderSync方法去动态生成模版,它主要做了以下几件事情

  • 创建目标文件夹,在哪个目录下调用这个命令,目标文件夹就是这个目录(target
  • 递归遍历模版文件夹(source
  • 根据命令行传入的参数(params)过滤掉一些不需要拷贝的文件
  • 如果是js文件,则调用getContentgetExt动态获取到内容跟文件拓展名
const copyFolderSync = (source, target, params) => {
  // 创建目标文件夹
  if (!fs.existsSync(target)) {
    fs.mkdirSync(target);
  }

  // 读取源文件夹
  const files = fs.readdirSync(source);

  // 遍历文件并逐一拷贝
  files.forEach(file => {
    const sourcePath = path.join(source, file);
    const targetPath = path.join(target, file);
    if (sourcePath.includes("api") && !params[DEPS.AXIOS.key]) {
      return;
    }
    if (sourcePath.includes("store") && !params[DEPS.MOBX.key]) {
      return;
    }
    if (
      (sourcePath.includes("tsconfig") || sourcePath.includes("vite-env")) &&
      params[PROMPT.LANG] !== ENUMS[PROMPT.LANG].TypeScript
    ) {
      return;
    }
    // 如果是目录,则递归拷贝
    if (fs.statSync(sourcePath).isDirectory()) {
      copyFolderSync(sourcePath, targetPath, params);
    } else {
      // 如果是文件,则直接拷贝
      const ext = path.extname(sourcePath);
      if (ext.substring(1) === "js") {
        const file = require(sourcePath);
        const { getContent, getExt } = file;
        const content = getContent(params);
        const ext = getExt(params);
        const fileInfo = path.parse(sourcePath);
        const name = `${fileInfo.name}.${ext}`;
        fs.writeFileSync(path.join(target, name), content, {
          encoding: "utf8",
        });
      } else {
        fs.copyFileSync(sourcePath, targetPath);
      }
    }
  });
};

以下是一个js文件的例子,旨在介绍params跟内容是如何交互的。

const { PROMPT, ENUMS } = require("../../src/constants");

const getContent = params => {
  return `
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.${
    params[PROMPT.LANG] === ENUMS[PROMPT.LANG].JavaScript ? "jsx" : "tsx"
  }";
import "./global.less";
ReactDOM.createRoot(document.getElementById("root")${
    params[PROMPT.LANG] === ENUMS[PROMPT.LANG].JavaScript ? "" : "!"
  }).render(<App />);
  `;
};
const getExt = params => {
  return params[PROMPT.LANG] === ENUMS[PROMPT.LANG].JavaScript ? "jsx" : "tsx";
};
module.exports = {
  getContent,
  getExt,
};

这里的实现方式基本上是根据参数拼接模版字符串,返回给调用方,动态生成文件。

代码美化

由于模版文件的内容是字符串拼接的,所以生成目标文件后不太好看。这里在生成完之后调用了prettier对目标文件夹进行了一次代码美化。

  • 递归处理文件夹和文件
  • 根据不同的文件名后缀选择不同的解释器
  • 把美化后的内容重新写到文件中
const getParser = filePath => {
  const ext = path.extname(filePath);

  switch (ext) {
    case ".js":
      return "babel";
    case ".ts":
      return "typescript";
    case ".jsx":
      return "babel";
    case ".tsx":
      return "typescript";
    case ".html":
      return "html";
    case ".json":
      return "json";
    default:
      return null; // 如果无法确定解析器,则返回 null
  }
};

const format = async folderPath => {
  const files = fs.readdirSync(folderPath);
  for (const file of files) {
    const filePath = path.join(folderPath, file);
    const isDirectory = fs.statSync(filePath).isDirectory();
    if (isDirectory) {
      await format(filePath);
    } else {
      const fileContent = fs.readFileSync(filePath, "utf-8");
      const parser = getParser(filePath);
      if (parser) {
        const formattedContent = await prettier.format(fileContent, {
          parser,
        });
        fs.writeFileSync(filePath, formattedContent, "utf-8");
      } else {
      }
    }
  }
};

发布到npm仓库

这个时候我们已经实现了这个脚手架工具,下面我们可以把它发布到npm仓库中,以便使用起来更加方便。如果你没有npm账号,可以去https://npmjs.com/ 注册一个账号。

然后命令行输入

  • npm login
  • npm publish

这样就可以发布到npm仓库中。这里需要关注的是你的package.json文件。

{
    //包名称
  "name": "@jayliang/vite-react-cli",
  //包的版本号
  "version": "1.0.0",
  "description": "基于vite跟react的脚手架工具",
  // 这里表示你的包是否是公开的,以及发布的地址是什么
  "publishConfig": {
    "access": "public",
    "registry": "https://registry.npmjs.org/"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  //我们发布的是命令行命令,所以这里需要定义一个bin对象
  "bin": {
    "create": "bin/create.js"
  },
  "keywords": [
    "cli",
    "vite",
    "react"
  ],
  "author": "jayliang",
  "license": "MIT",
  "dependencies": {
    "inquirer": "^8.2.2",
    "prettier": "^3.1.1"
  }
}

成功发布到npm仓库之后,可以使用npm i -g @jayliang/vite-react-cli去安装这个包,然后执行命令npx @jayliang/vite-react-cli,就可以愉快的创建项目了~

最后

本文纯属抛砖引玉,提供一个自定义脚手架的思路。如果你也有这样的需求,可以参考本文的思路去实现。欢迎评论区交流~


相关文章
|
2月前
|
移动开发 小程序 数据可视化
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
340 3
|
2月前
|
资源调度 JavaScript 前端开发
如何实现一个类似 vite 的脚手架并发布 npm
本文介绍了如何实现一个类似 Vite 的脚手架工具。通过详细解析和实践,文章分享了从零开始构建脚手架的过程,包括技术选型、开发步骤及发布 NPM 包的完整流程。最终目标是让用户能够通过 `yarn create electron-prokit myapp` 快速搭建 Electron 项目。项目源码可在 GitHub 上获取。
34 5
|
2月前
|
前端开发 JavaScript API
自己动手封装axios通用方法并上传至私有npm仓库:详细步骤与实现指南
自己动手封装axios通用方法并上传至私有npm仓库:详细步骤与实现指南
117 0
|
2月前
|
前端开发 JavaScript 开发工具
从零开始:构建、打包并上传个人前端组件库至私有npm仓库的完整指南
从零开始:构建、打包并上传个人前端组件库至私有npm仓库的完整指南
385 0
|
2月前
|
资源调度 前端开发 安全
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
前端实战:基于Verdaccio搭建私有npm仓库,轻松上传与下载自定义npm插件包
129 0
|
4月前
|
存储 安全 Java
阿里云云效产品使用合集之怎么设置使用npm私有仓库进行流水线拉取依赖
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
7月前
|
前端开发 开发者
NPM包脚手架:开启前端开发新纪元
NPM包脚手架:开启前端开发新纪元
151 0
|
7月前
|
JavaScript
无法安装Vue脚手架 npm install @vue/cli -g
无法安装Vue脚手架 npm install @vue/cli -g
220 0
|
7月前
|
前端开发 JavaScript 开发者
探索npm的高级特性:自定义脚本与包的发布与维护
探索npm的高级特性:自定义脚本与包的发布与维护
|
2月前
|
缓存 资源调度 JavaScript
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
npx与npm的差异解析,以及包管理器yarn与Node版本管理工具nvm的使用方法详解
65 0