前端脚手架实现与内部npm发布-2023新

764 阅读8分钟

简介

脚手架本质是一个运行在操作系统上的一个工具、一个客户端。它通过命令执行。

意义

  1. 摆脱构建工程时的重复性工作。一行命令初始化好一个项目。
  2. 将研发过程:自动化、标准化、可量化[直接评估业务开发即可]
    • 统一目录结构
    • 统一工作流程
    • 统一配置,降低管理成本
    • 实现项目0配置

自己维护一套脚手架,可控性高,能满足团队特定需求的研发。

命令

命令由四个部分组成:

  1. 主命令
  2. 子命令
  3. 参数
  4. 配置(option)

例如:vue create my-app

它是vue-cli脚手架创建一个my-app项目的命令。空格分开的分别对应 主、子、参三个部分。

例如:vue create my-app --force

这里的 --force 就是option部分,用来辅助在某个参数场景下用户的一种细分选择。

ps: 一些option也是可以简写的。比如:--registry 简写:-r

脚手架执行原理

  1. 终端输入 vue create vue-test
  2. 终端解析出vue命令
  3. 终端在环境变量中找到vue命令
  4. 终端根据vue命令链接到实际文件vue.js
  5. 终端利用node执行vue.js
  6. vue.js 解析 command/options
  7. vue.js 执行 command
  8. 执行完毕,退出执行

设计思路

  1. 解耦:脚手架与模板分离
  2. 脚手架负责构建流程,通过命令行与用户交互,获取项目信息
  3. 模板负责统一项目结构、工作流程、依赖项管理
  4. 脚手架需要检测模板的版本是否有更新,支持模板的删除与新建

可 参考vue-cli。


脚手架实现过程

  1. 定制脚手架的主命令(node本身就支持);
  2. 添加子命令、选项,同时解析命令参数(commander);
  3. 与用户交互(用户输入,选择),发问后接受用户应答(inquirer);
  4. 根据用户输入的项目名称,模板来下载(axios/request/download-git-repo) ;
  5. 解压模板(decompress)
  6. 修改模板里边的文件(package.json,index.html等)(fs.writeFileSync) ;
  7. 为项目安装依赖,结束(child_process.spawn 开启一个子进程,执行下载)。

node.js 内置了对命令行操作的支持,package.json 中的 bin 字段可以定义命令名和关联的执行文件。在 package.json 中添加 bin 字段

一. 初始化项目

生成一个package.json

npm init -y

二. 配置bin

bin:配置内部命令对应的可执行文件位置,配置命令后,npm 会寻找到对应的可执行文件,然后在 node_modules/.bin 目录下建立对应的符号链接。 由于 node_modules/.bin 会在运行时候加入到系统的环境变量,因此我们可以通过 npm 调用命令来执行脚本。 所有 node_modules/.bin 目录下的命令都可以通过 npm run [命令] 执行。 所以我们需要在 package.json 配置入口:

"bin": {
    "vue3-cli": "./bin/vue3-cli.js"
  },

然后创建bin/vue3-cli.js文件,添加测试代码: 注意:第一行代码必须写。告知此文件用node去解析。

#!/usr/bin/env node

console.log('hello I am web cli base vue3!')

三. 全局安装下本地这个项目

在当前项目目录下执行

npm install .  -g

或者,

sudo npm link

在开发公司内部npm包时候,为了提升开发效率,不想每次先提交git,再执行 npm install 命令来看效果,npm link 可以帮助我们很方便的实现这样的功能。

image.png 然后可以测试下此命令是否生效,任意目录下开cmd,输入:

image.png

发现可以正常执行打印出了log.

ps: 如果你是windows用户,或是远程虚拟机办公的场景,你一定是需要管理员权限的,不然npm link注册失败。

image.png

运行也会失败。

image.png

这时候,我们管理身份进入系统,通过cmd去做相关操作发现可行。

四. 为命令加参数

上面的步骤,已经在系统里成功的添加了自己的命令。那如何丰富我们的命令,可以做更多的事情呢。接下来,需要借助工具 commander 帮我们加参数了。

4.1 安装

npm i commander@11 -S

4.3 书写测试代码

我们可以先实现一个简单的功能。 为命令添加一个 查看版本 的参数选项。

bin/vue3-cli.js

#!/usr/bin/env node
// console.log('hello I am web cli base vue3!')

const {Command} = require("commander")
const pkg = require("../package.json")

const program = new Command()

program.version(pkg.version,"-v --version")

program.parse()

运行:

image.png

4.4 定义子命令

主命令,由node自身定义好了,为了丰富主命令的功能,我们开支散叶,定制子命令。

bin/vue3-cli-create.js

console.log('I am sub command!')

入口主程序:bin/vue3-cli.js

#!/usr/bin/env node

const {Command} = require("commander")
const pkg = require("../package.json")

const program = new Command()

// 选项
program.version(pkg.version,"-v --version")


// 子命令
program.command('create [projectName]','create a new project')//[projectName] 是可选参

program.parse(process.argv)

运行:

image.png

五. 其他生态插件集成

5.1 安装

npm i inquirer@7.3  chalk@4 ora@5 decompress@4 request@2.8   -S

注意: 这里一般都指定了大版本,这里这些版本的node包依旧是遵循commonJs规范的,也就是使用require()语法导入的。默认安装最新包的话,一般都是使用ESM规范import导入了。

5.2 create子命令书写

bin/vue3-cli-create.js

// console.log('I am sub command!')
const {Command} = require("commander")
const inquirer = require("inquirer")
const chalk = require("chalk")

const program = new Command()

let projectName = undefined;
let force = undefined;

// create 子命名
program
    .arguments("[projectName]")
    .description("初始化项目")
    .option("-f --force", "如果存在输入的项目目录,强制删除项目目录")
    .action((name,cmd)=>{
        projectName = name
        force = cmd.force
        console.log('come in');
    })

program.parse(process.argv)


// 设置交互问题
const questions = [
    {
       type:"input",
       name:"projectName",
       message:chalk.yellow("请输入你的项目名称") 
    },{
        type:"list",
        name:"template",
        choices:[
            {name:"webpack5-vue2-template",value:"vue2-webpack5-admin"},
            {name:"vite-vue3-template",value:"vite-vue3-template"},
            {name:"webpack-react-template",value:"lb-react-apps-template"}
        ]
    }
]


// 执行与用户的交互
inquirer
    .prompt(questions)
    .then((answers)=>{

        if(answers.projectName){
            projectName = answers.projectName
        }

        const templateName = answers.template

        if(!templateName || !projectName){
            // 退出
            console.log("参数不传递,直接退出了")
        }else{
            // go to download
            console.log(" 我去下载模板")
        }



    })




运行:

image.png

5.4 下载模板

从上面 5.3 我们把下载流程代码写完了。下载过程我们可以进一步细化,同时下载后我们还有做一些事情来增强我们的脚手架。

image.png

六. 书写全部程序

按照流程图,我们一一实现.

七. 发布脚手架到npm

这里npm服务是采用 nexus3 版本搭建的。这个东西是一款对java里 Maven 包管理的私服工具,同时他还支持 npm 、docker 、yum 等等。搭建好后,需要做的配置工作:

ps:如果你已经配置好了,直接看 步骤八.

步骤一 首先,登录后,setting/创建存储/创建 npm Repositories。

然后可以看到3个npm:

  • npm (proxy) 代理仓库 [代理到npm or 淘宝npm 的公共包]
  • npm (hosted) 是私有仓库 [自己发布的包]
  • npm (group) 是组合前面两个,最终暴露出的 [供自己本地下载安装的大库]

步骤二 设置代理仓库

  • Name: 仓库的名字
  • Remote storage: 远程仓库地址
  • Blob store:选择我们刚刚创建好的存储

image.png

我们自己的npm服会定期去淘宝源里取包。

步骤三 创建hosted仓库

image.png

步骤四 创建group仓库

image.png

注意下 Members 的顺序,下载npm包时候会按照这个顺序先去私有库hosted里找,找不到再去代理库里查找。所以即便是重名也会优先下载私服的模块。

步骤五 创建用户

这里可以创建一个新用户,也可以使用管理员账号直接登录发布。

步骤六 配置.npmrc文件

取的是group的地址:

image.png

registry=http://192.168.x.y:8081/repository/npm-group/

步骤七 配置package.json

取的是hosted的地址:

image.png

"publishConfig": {
    "registry": "http://192.168.x.y:8081/repository/npm-hosted"
},

步骤八 发布

前期的准备工作完毕。接着我们需要把这个工具发布到npm 上,供公司内部开发同事下载使用。发布到内部的npm服上过程:

1.切换本地npm代理源:

npm config set registry https://192.168.x.y:8081/repository/npm-group

2.要发布的项目包下开cmd ,然后登录:npm login输入账号密码,注册邮箱。

3.npm publish

当然,如果你使用命令发布失败了。也可以选择自己手动上传npm包。过程如下:

1.改package.json的版本号。每次发布前都要给个新值。

2.要发布的项目包下开cmd ,然后npm pack

3.登录到npm网页版,选择 upload ,进入 npm-hosted选择刚打包的文件即可。

image.png


扩展说明

commander API

  1. usage(): 设置 usage 值
  2. command(): 定义一个命令名字
  3. description(): 设置 description 值
  4. option(): 定义参数,需要设置“关键字”和“描述”,关键字包括“简写”和“全写”两部分,以”,”,”|”,”空格”做分隔。
  5. parse(): 解析命令行参数 argv
  6. action(): 注册一个 callback 函数
  7. version() : 终端输出版本号

inquirer API

  • type:表示提问的类型,包括:input, confirm, list, rawlist, expand, checkbox, password, editor;
  • name: 存储当前问题回答的变量;
  • message:问题的描述;
  • default:默认值;
  • choices:列表选项,在某些 type 下可用,并且包含一个分隔符(separator);
  • validate:对用户的回答进行校验;
  • filter:对用户的回答进行过滤处理,返回处理后的值;
  • when:根据前面问题的回答,判断当前问题是否需要被回答;
  • prefix:修改 message 默认前缀;
  • suffix:修改 message 默认后缀。

reuqest库已经弃用

npm 上已经search 不到了。一个紧随 nodeJs 诞生的 请求库由于一些原因弃用了。

image.png

弃用原因点这里

看下它的起源,已经有十几年历史了。

image.png

模板源切到公司内部gitlab

实际应用上来看,大部分是公司内部项目。所以你的模板源大概率是不会开源的。都是托管在公司内部的代码仓库,这里以 内部的gitlab 举例子。这里可以参照另外一篇文章:脚手架gitlab模板下载


参考文章:

认识node核心模块--从Buffer、Stream到fs

npm 私有模块的3种方法