写一个属于自己的脚手架哪有那么难

144 阅读4分钟

引言

下面是用脚手架的初始化项目的例子。

截屏2022-06-18 上午10.43.28.png

当你想做一个脚手架的时候,看到这些可以选择的步骤,是不是无从下手,感觉快要被劝退了,其实你想要的东西都可以在npm找得到,下面教大家创建一个简单的脚手架的流程,这里都用到的上层建筑(用的都是npm库,门槛低😊),附上我的仓库

第一步 创建项目

来看看目录结构

截屏2022-06-18 上午11.03.01.png

都2022年,不会还有人不用typescript吧,用typescript是有好处的,至少可以体验丰富的代码提示,core是核心代码,通过rollup进行打包,用pnpm进行包管理,template是需要用到的模版,eslint管理代码格式

第二步 选项提示

像我们用vue-cli的时候都会有那些选项提示,可以去配置不同的东西,那我们可以用到一个库prompts,下面使用这个库的方式,删减了一点,具体可以去看我的仓库

// core/initOptions.ts

import prompts from 'prompts'
import type { PromptObject } from 'prompts'

interface result {
  projectName?: string
  hasProjectDir?: boolean
  pickPresets?: string
}

const options: PromptObject[] = [
  {
    name: 'projectName',
    type: 'text',
    message: 'Project Name:',
    initial: 'create-app'

  },
  {
    name: 'pickPresets',
    type: 'select',
    message: '请选择一个预设',
    choices: [
      { title: 'vue3 ', value: 'vue' },
      { title: 'react ', value: 'react' }
    ]
  }
]

const initOptions = async () => {
  try {
    const result: result = await prompts(options,
      {
        onCancel: () => {
          throw new Error('取消操作')
        }
      })
    return result // 这里返回的是已经选好的 optios 对象
  } catch (err: any) {
    process.exit(1) // 退出进程 1 代表失败
  }
}

export default initOptions

第三步 编写模版

我们先来简单的,这里新建一个template存放我们的模版,根据上面选好的选项,去判断我们接下来要用哪个模版,我们来思考一下,怎么把文件夹复制到我们创建的文件夹呢,我们首先想到的是用fs模块,去找这个文件夹有没有,有就进入里面去一步步遍历,如果自己想磨练nodeapi的话可以这么做,灵活性很高,如果不想那么麻烦,这里推荐一个fs-extra,这个库可以把一整个文件夹都复制过去,这样我们的难度有降低了很多。

// core/execute.ts

import { green, bold } from 'kolorist'
import { removeSync, ensureDirSync, copySync } from 'fs-extra'
import { join, resolve, relative } from 'path'
import { result } from './type'

const cwd = process.cwd() // 获取node进程的当前工作目录

type template ='typescript-react' |'typescript-vue'
const templatePath = (...dir:template[]) => resolve(__dirname, '../template', ...dir)

const execute = (options:result) => {
  const { projectName, pickPresets, hasProjectDir } = options
  const root = join(cwd, projectName!)

  if (hasProjectDir) {
    removeSync(root)
  } else {
    ensureDirSync(root)
  }

  if (pickPresets === 'react') {
    copySync(templatePath('typescript-react'), root)
  }
  if (pickPresets === 'vue') {
    copySync(templatePath('typescript-vue'), root)
  }

  console.log('\nDone. Now run:\n')

  if (root !== cwd) {
    console.log(`  ${bold(green(`cd ${relative(cwd, root)}`))}`)
  }
  console.log(`  ${bold(green('pnpm install'))}\n\n`)
}

export default execute

当我们逻辑写完了,怎么去显示那个cd projectpnpm install这几个字,其实看上面这都是console.log的产物,我们可以用kolorist去变换颜色,然后用ts-node执行index.ts文件就能看出效果啦

第四步 npm link测试是否正常

发布npm包之前,我们可以先在本地测试,首先要在package.josn里面加bin字段,用于运行这个脚本的指令,具体我也不是很清楚里面的原理,不过使用方法可以看这里 ,mian就指向你打包后的文件,具体配置看下面

{
  "name": "swift-app",
  "version": "1.0.3",
  "description": "通过cli创建自定义模版",
  "main": "/dist/index.js",
  "bin": {
    "swift-app": "dist/index.js"
  }
 }

还有一个要注意的就是打包生成的文件#!/usr/bin/env node这段命令是会打包到模块里面的,到时候运行的时候就会报错,找不到可执行的命令,这个也很好解决

// rollup.config.js
export default {
  input: 'core/index.ts',
  output: {
    dir: 'dist',
    format: 'cjs',
    banner: '#!/usr/bin/env node'
  }
}

然后通过npm link去做包链接,然后再执行bin的名称就可以运行啦,没问题就发布到npm就可以使用啦

遇到的问题

tsconfig配置

老实说,我也不懂ts的配置,去官网找吧,又不是看得很明白,导致运行时一堆报错,然后我发现了文档已经有很多预设了,然后extends用就好了,compilerOptions就不用配了,运行后也没有报错了

截屏2022-06-18 下午12.01.04.png

rollup打包ts问题

之前第一版的时候用的是webpack打包,之后觉得打包出来的文件格式不够美观,就换成了rollup了,rollup的配置也是超级简单,唯一遇到的问题就是下面的这种报错

截屏2022-06-18 下午12.10.55.png

截屏2022-06-18 下午12.12.00.png 于是查了查官网,发现rollup是不会解析你用到的依赖在哪,不会帮你引进来,不像webpack一样会根据你用到的依赖一层一层进去找形成依赖图,下面是官网说的

截屏2022-06-18 下午12.22.01.png 所以现在要用到两个库rollup-plugin-node-resolverollup-plugin-commonjs,这两个库已经被转移了,我们要在这基础上改改@rollup/plugin-node-resolve@rollup/plugin-commonjs

@rollup/plugin-node-resolve 是用来告诉 Rollup 如何查找外部模块
@rollup/plugin-commonjs 是用来将 CommonJS 模块转换为 ES6

最后

这算是给新手搭建一个简单的脚手架方式吧,不用借助任何第三方脚手架工具,像yeoman这些,希望这个脚手架流程能对你们有帮助