借鉴create-vite搭建自己的创建项目工具(2)项目搭建与发包

上一篇文章借鉴create-vite搭建自己的创建项目工具(1)源码分析已经简单介绍了create-vite的源码知识,现在就来搭建自己的项目吧。

项目初始化

首先咱们先新建一个文件夹,然后执行

csharp 复制代码
pnpm init

新建一个package.json文件。修改一下name和description。加一个type:"module"

json 复制代码
{
	"name":"xxx",
	"description":"description",
	"type": "module",
	...
}

接下来咱们在根目录新建以下文件夹

  • src文件夹,里面新建一个index.ts文件。
  • template文件夹,里面存放各种模板
  • docs文件夹,里面存放使reamdme文件
  • index.js 入口文件,里面引入dist下的打包内容
javascript 复制代码
#!/usr/bin/env node

import './dist/index.mjs'

script配置

接下来咱们写scripts命令,因为要用到unbuild嘛,所以先安装一下,注意这里因为要安装到开发环境所以要加 -D。

复制代码
pnpm install unbuild -D

然后再在根目录新建一个build.config.ts的配置文件进行配置。这里直接粘出我的配置。

php 复制代码
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries:["src/index"],
  clean: true,
  rollup: {
    inlineDependencies: true,
    esbuild: {
      minify: true,
    },
  },
  alias: {
    prompts: 'prompts/lib/index.js',
  },
})

这里有需要用到prompts,咱们先安装上

复制代码
pnpm install prompts -D

接下来写script命令,这里直接粘create-vite里面的命令就好

bash 复制代码
 "scripts": {
  "build": "unbuild",
  "dev": "unbuild --stub",
  "lint": "eslint --cache .",
  "test": "echo "Error: no test specified" && exit 1",
  "prepublishOnly": "npm run build"
},

eslint和prettier

我习惯把eslint和prettier也安排上,规范代码格式。这个看个人需求。我安装的eslint用的是这种方式,应该要全局安装一下eslint

csharp 复制代码
npx eslint --init

然后就会让你选择配置,我是这样选择的,这个页根据个人需求来定。仅供参考。

安装好后需要在根目录添加一个.eslintignore文件。做忽略检查用

bash 复制代码
//.eslintignore文件
/node_modules
/dist
/package-lock.json
.DS_Store

这个是我的配置。

prettier的安装也很简单

复制代码
pnpm install prettier -D

新建一个.prettierrc文件,里面配置一下,粘出我的配置项做参考

json 复制代码
{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "singleQuote": true,
  "semi": false,
  "trailingComma": "none",
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "bracketSameLine": false,
  "requirePragma": false,
  "endOfLine": "auto"
}

再新建一个.prettierignore文件

bash 复制代码
# Ignore artifacts:
build
coverage

# Ignore all HTML files:
*.html

**/.git
**/.svn
**/.hg
**/node_modules 
**/dist

到现在基础的配置已经完成啦。现在咱们开始去src下的index.ts里面开发

index.ts

在开发之前需要先去安装一下@types/node和@types/prompts声明文件,因为是用ts开发嘛,声明文件必不可少。

bash 复制代码
pnpm install @types/node -D 
pnpm install @types/prompts -D

安装完后咱们新建一些全局变量

arduino 复制代码
const defaultTargetDir = 'new-project'
let targetDir = defaultTargetDir
const cwd = process.cwd()
const renameFiles: Record<string, string | undefined> = {
  _gitignore: '.gitignore'
}

const TEMPLATELIST: Template[] = [
  {
    name: 'vue',
    display: 'Vue+TS+Pinia',
    color: green,
    variants: [
      {
        name: 'custom',
        display: 'Custom Layout',
        color: magenta
      },
      {
        name: 'vue',
        display: 'Vue+TS+Pinia Project',
        color: lightGreen
      },
      {
        name: 'order',
        display: 'Order Layout Project',
        color: lightGreen
      }
    ]
  },
  {
    name: 'uni',
    display: 'Vue+TS+uniApp',
    color: blue
  }
]

这些变量都是为init函数里面服务的。

新建一个init函数,这个就是我们的核心方法

csharp 复制代码
function init(){}

这里面我就做了三件事

  1. 执行prompts,拿到交互结果
  2. 新建文件夹找到对应模板进行读写
  3. 提示结果

为了方便我直接在代码里以注释形式分析

typescript 复制代码
async function init() {
  try {
		// 获取到命令行交互结果 
    const result: prompts.Answers<'projectName' | 'template' | 'layout'> = await handlePrompts()
    const { projectName, template, layout } = result

    const root = path.join(cwd, targetDir)
  	// 创建一个新文件夹
    fs.mkdirSync(root, { recursive: true })
  	// 寻找模板
    const templateDir = path.resolve(
      fileURLToPath(import.meta.url),
      '../../template',
      `template-${layout || template.name}`
    )
  	// 开始写入
    const write = (file: string, content?: string) => {
      const targetPath = path.join(root, renameFiles[file] ?? file)
      if (content) {
        fs.writeFileSync(targetPath, content)
      } else {
        copy(path.join(templateDir, file), targetPath)
      }
    }

    const files = fs.readdirSync(templateDir)
    for (const file of files.filter(f => f !== 'package.json')) {
      write(file)
    }
  	// 找到package.json更改内容
    const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'))

    pkg.name = projectName
    write('package.json', JSON.stringify(pkg, null, 2) + '\n')
    const cdProjectName = path.relative(cwd, root)
    console.log(`\nDone. Now run:\n`)
  	// 提示成功
    if (root !== cwd) {
      console.log(`  cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`)
      console.log(`  pnpm install`)
      console.log(`  pnpm run dev`)
    }
  } catch (error) {
    console.log(`error`, error)
  }
}

init().catch(e => {
  console.error(e)
})

我这里开发的很简单,因为是内部用嘛,就没考虑的那么复杂。偷了个懒哈哈。

全部代码贴出来,不多,不到180行,很好理解

typescript 复制代码
import path from 'node:path'
import fs from 'node:fs'
import { fileURLToPath } from 'node:url'
import prompts from 'prompts'

import { blue, cyan, green, lightGreen, lightRed, magenta, red, reset, yellow } from 'kolorist'

type ColorFunc = (str: string | number) => string
type Template = {
  name: string
  display: string
  color: ColorFunc
  variants?: LayoutVariant[]
}
type LayoutVariant = {
  name: string
  display: string
  color: ColorFunc
  customCommand?: string
}

const defaultTargetDir = 'new-project'
let targetDir = defaultTargetDir
const cwd = process.cwd()

const renameFiles: Record<string, string | undefined> = {
  _gitignore: '.gitignore'
}

const TEMPLATELIST: Template[] = [
  {
    name: 'vue',
    display: 'Vue+TS+Pinia',
    color: green,
    variants: [
      {
        name: 'custom',
        display: 'Custom Layout',
        color: magenta
      },
      {
        name: 'vue',
        display: 'Vue+TS+Pinia Project',
        color: lightGreen
      },
      {
        name: 'order',
        display: 'Order Layout Project',
        color: lightGreen
      }
    ]
  },
  {
    name: 'uni',
    display: 'Vue+TS+uniApp',
    color: blue
  }
]

async function handlePrompts() {
  const result = await prompts(
    [
      {
        type: 'text',
        name: 'projectName',
        message: reset('项目名称:'),
        initial: defaultTargetDir,
        onState: state => {
          targetDir = formatTargetDir(state.value) || defaultTargetDir
        }
      },
      {
        type: 'select',
        name: 'template',
        message: reset('请选择一个模板:'),
        initial: 0,
        choices: TEMPLATELIST.map(template => {
          const tColor = template.color
          return {
            title: tColor(template.display || template.name),
            value: template
          }
        })
      },
      {
        type: (template: Template) => (template && template.variants ? 'select' : null),
        name: 'layout',
        message: reset('选择一个layout'),
        initial: 0,
        choices: (template: Template) =>
          template.variants?.map(variant => {
            const VColor = variant.color
            return {
              title: VColor(variant.display || variant.name),
              value: variant.name
            }
          })
      }
    ],
    {
      onCancel: () => {
        throw new Error(red('✖') + ' Operation cancelled')
      }
    }
  )
  return result
}

function formatTargetDir(targetDir: string | undefined) {
  return targetDir?.trim().replace(//+$/g, '')
}

function copy(src: string, dest: string) {
  const stat = fs.statSync(src)
  if (stat.isDirectory()) {
    copyDir(src, dest)
  } else {
    fs.copyFileSync(src, dest)
  }
}

function copyDir(srcDir: string, destDir: string) {
  fs.mkdirSync(destDir, { recursive: true })
  for (const file of fs.readdirSync(srcDir)) {
    const srcFile = path.resolve(srcDir, file)
    const destFile = path.resolve(destDir, file)
    copy(srcFile, destFile)
  }
}

async function init() {
  try {
    const result: prompts.Answers<'projectName' | 'template' | 'layout'> = await handlePrompts()
    const { projectName, template, layout } = result

    const root = path.join(cwd, targetDir)
    fs.mkdirSync(root, { recursive: true })
    const templateDir = path.resolve(
      fileURLToPath(import.meta.url),
      '../../template',
      `template-${layout || template.name}`
    )

    const write = (file: string, content?: string) => {
      const targetPath = path.join(root, renameFiles[file] ?? file)
      if (content) {
        fs.writeFileSync(targetPath, content)
      } else {
        copy(path.join(templateDir, file), targetPath)
      }
    }
    console.log(`templateDir`, templateDir)
    const files = fs.readdirSync(templateDir)
    for (const file of files.filter(f => f !== 'package.json')) {
      write(file)
    }

    const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'))

    pkg.name = projectName
    write('package.json', JSON.stringify(pkg, null, 2) + '\n')
    const cdProjectName = path.relative(cwd, root)
    console.log(`\nDone. Now run:\n`)
    if (root !== cwd) {
      console.log(`  cd ${cdProjectName.includes(' ') ? `"${cdProjectName}"` : cdProjectName}`)
      console.log(`  pnpm install`)
      console.log(`  pnpm run dev`)
    }
  } catch (error) {
    console.log(`error`, error)
  }
}

init().catch(e => {
  console.error(e)
})

在开发的时候呢就先执行pnpm run dev,这样每次修改都会打包到dist文件。查看结果时就可以执行

bash 复制代码
node dist/index.mjs

查看结果。

发布npm包

  1. 先查看一下npm的registry看看是不是官方源registry.npmjs.org/
arduino 复制代码
npm config get registry

如果不是就改一下

arduino 复制代码
npm config set registry https://registry.npmjs.org
  1. 登录npm

    npm login

  2. 打包项目(这里一定要打包,用开发环境发上去的包会有问题,踩过坑!)

arduino 复制代码
pnpm run build
  1. 更新package.json中的版本号
json 复制代码
{
  "version": "1.0.0",
}
  1. 根目录package.json添加bin命令,这个可以让使用者下载依赖包后直接在命令行执行脚本,这里举例。任意取名字
json 复制代码
"bin": {
  "xxx": "index.js",
  "create-xxx": "index.js"
},
  1. 根目录package.json添加files字段,限制上传内容
json 复制代码
 "files": [
    "index.js",
    "template/*",
    "dist"
  ],
  1. 上传文件

    npm publish

以上就是这个项目的打包过程。

结语

这就是我根据create-vite搭建自己的一个创建项目的小工具,如果你也有类似的需求可以参考一下这个项目。希望能够帮助到大家。

相关推荐
zengyuhan50326 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休29 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running38 分钟前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔38 分钟前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户44455436542640 分钟前
Android的自定义View
前端
WILLF41 分钟前
HTML iframe 标签
前端·javascript
枫,为落叶1 小时前
Axios使用教程(一)
前端
小章鱼学前端1 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah1 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
流星稍逝1 小时前
手搓一个简简单单进度条
前端