最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

create?vite?實(shí)例源碼解析

 更新時(shí)間:2022年11月27日 09:22:31   作者:田八  
這篇文章主要為大家介紹了create?vite?實(shí)例源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

代碼結(jié)構(gòu)

create-vite的源碼很簡(jiǎn)單,只有一個(gè)文件,代碼總行數(shù)400左右,但是實(shí)際需要閱讀的代碼大約只有200行左右,廢話不多說(shuō),直接開(kāi)始吧。

create-vite的代碼結(jié)構(gòu)非常簡(jiǎn)單,直接將index.ts拉到最底下,發(fā)現(xiàn)只執(zhí)行了一個(gè)函數(shù)init()

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

我們的故事將從這里開(kāi)始。

init()

init()函數(shù)的代碼有點(diǎn)長(zhǎng),但是實(shí)際上也不復(fù)雜,我們先來(lái)看看它最開(kāi)頭的兩行代碼:

async function init() {
  const argTargetDir = formatTargetDir(argv._[0])
  const argTemplate = argv.template || argv.t
}

首先可以看到init函數(shù)是一個(gè)異步函數(shù),最開(kāi)始的兩行代碼分別獲取了argv._[0]argv.template或者argv.t;

這個(gè)argv是怎么來(lái)的,當(dāng)然是通過(guò)一個(gè)解析包來(lái)解析的,在頂部有這樣的一段代碼:

const argv = minimist(process.argv.slice(2), { string: ['_'] })

就是這個(gè)minimist包,它的作用就是解析命令行參數(shù),感興趣的可以自行了解,據(jù)說(shuō)這個(gè)包也是百來(lái)行代碼。

繼續(xù)往下,這兩個(gè)參數(shù)就是我們?cè)趫?zhí)行create-vite命令時(shí)傳入的參數(shù),比如:

create-vite my-vite-app

那么argv._[0]就是my-vite-app;

如果我們執(zhí)行的是:

create-vite my-vite-app --template vue

那么argv.template就是vue

argv.t就是argv.template的簡(jiǎn)寫(xiě),相當(dāng)于:

create-vite my-vite-app --t vue
# 等價(jià)于
create-vite my-vite-app --template vue

通過(guò)打斷點(diǎn)的方式,可以看到結(jié)果和我們預(yù)想的一樣。

formatTargetDir(argv._[0])就是格式化我們傳入的目錄,它會(huì)去掉目錄前后的空格和最后的/,比如:

formatTargetDir(' my-vite-app ') // my-vite-app
formatTargetDir(' my-vite-app/') // my-vite-app

這個(gè)代碼很簡(jiǎn)單,就不貼出來(lái)了,繼續(xù)往下:

let targetDir = argTargetDir || defaultTargetDir

targetDir是我們最終要?jiǎng)?chuàng)建的目錄,defaultTargetDir的值是vite-project,如果我們沒(méi)有傳將會(huì)用這個(gè)值來(lái)兜底。

緊接著后面跟著一個(gè)getProjectName的函數(shù),通常來(lái)講這種代碼可以跳過(guò)先不看,但是這里的getProjectName函數(shù)有點(diǎn)特殊;

const getProjectName = () =>
  targetDir === '.' ? path.basename(path.resolve()) : targetDir

它會(huì)根據(jù)targetDir的值來(lái)判斷我們的項(xiàng)目是不是在當(dāng)前目錄下創(chuàng)建的,如果是的話,就會(huì)返回當(dāng)前目錄的名字,比如:

create-vite .

可以看到如果項(xiàng)目名稱(chēng)傳的是.,那么getProjectName函數(shù)就會(huì)返回當(dāng)前目錄的名字,也就是create-vite(根據(jù)自己的情況而定);

不看源碼還真不知道這里還可以這么用,繼續(xù)往下,就是定義了一個(gè)問(wèn)題數(shù)組:

result = await prompts([])

這個(gè)prompts函數(shù)是一個(gè)交互式命令行工具,它會(huì)根據(jù)我們傳入的問(wèn)題數(shù)組來(lái)進(jìn)行交互,就比如源碼中,一共列出了6個(gè)問(wèn)題:

  • projectName:項(xiàng)目名稱(chēng)
  • overwrite:是否覆蓋已存在的目錄
  • overwriteChecker:檢測(cè)覆蓋的目錄是否為空
  • packageName:包名
  • framework:框架
  • variant:語(yǔ)言

當(dāng)執(zhí)行create-vite命令時(shí),后面不跟著任何參數(shù),而且我們一切操作都是合規(guī)的,那么只會(huì)經(jīng)歷三個(gè)問(wèn)題:

  • projectName:項(xiàng)目名稱(chēng)
  • framework:框架
  • variant:語(yǔ)言

projectName:項(xiàng)目名稱(chēng)

配置項(xiàng)如下:

var projectName = {
  type: argTargetDir ? null : 'text',
  name: 'projectName',
  message: reset('Project name:'),
  initial: defaultTargetDir,
  onState: (state) => {
    targetDir = formatTargetDir(state.value) || defaultTargetDir
  }
}

先來(lái)簡(jiǎn)單介紹一個(gè)每一個(gè)配置項(xiàng)的含義:

  • type:?jiǎn)栴}的類(lèi)型,這里的null表示不需要用戶(hù)輸入,直接跳過(guò)這個(gè)問(wèn)題,這個(gè)配置項(xiàng)的值可以是textselect、confirm等,具體可以看這里;
  • name:?jiǎn)栴}的名稱(chēng),這里的projectName是用來(lái)在prompts函數(shù)的返回值中獲取這個(gè)問(wèn)題的答案的;
  • message:?jiǎn)栴}的描述,這里的Project name:是用來(lái)在命令行中顯示的;
  • initial:?jiǎn)栴}的默認(rèn)值,這里的defaultTargetDir是用來(lái)在命令行中顯示的;
  • onState:?jiǎn)栴}的回調(diào)函數(shù),每次用戶(hù)輸入的時(shí)候都會(huì)觸發(fā)這個(gè)函數(shù),這里的state就是用戶(hù)輸入的值;

可以看到這里的type配置是根據(jù)argTargetDir的值來(lái)決定的,如果argTargetDir有值,那么就會(huì)跳過(guò)這個(gè)問(wèn)題,直接使用argTargetDir的值作為項(xiàng)目名稱(chēng);

如果在使用create-vite命令時(shí),后面跟著了項(xiàng)目名稱(chēng),那么argTargetDir就有值了,也就是會(huì)跳過(guò)這個(gè)問(wèn)題,后面的屬性就沒(méi)什么好分析了,接著往下。

overwrite:是否覆蓋已存在的目錄

配置項(xiàng)如下:

var overwrite = {
  type: () =>
    !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
  name: 'overwrite',
  message: () =>
    (targetDir === '.'
      ? 'Current directory'
      : `Target directory "${targetDir}"`) +
    ` is not empty. Remove existing files and continue?`
}

這里的type配置項(xiàng)是一個(gè)函數(shù),這個(gè)函數(shù)的返回值是null或者confirm

如果targetDir目錄不存在,或者targetDir目錄下面沒(méi)有東西,那么就會(huì)跳過(guò)這個(gè)問(wèn)題,直接使用null作為type的值;

message配置項(xiàng)也是一個(gè)函數(shù),這個(gè)函數(shù)的返回值是一個(gè)字符串,這個(gè)字符串就是在命令行中顯示的內(nèi)容;

同樣因?yàn)槿诵曰目紤],會(huì)顯示不同的提示語(yǔ)來(lái)幫助用戶(hù)做出選擇;

overwriteChecker:檢測(cè)覆蓋的目錄是否為空

配置項(xiàng)如下:

var overwriteChecker = {
  type: (_, {overwrite}: { overwrite?: boolean }) => {
    if (overwrite === false) {
      throw new Error(red('?') + ' Operation cancelled')
    }
    return null
  },
  name: 'overwriteChecker'
}

overwriteChecker會(huì)在overwrite問(wèn)題之后執(zhí)行,這里的type配置項(xiàng)是一個(gè)函數(shù),里面接收了兩個(gè)參數(shù);

第一個(gè)參數(shù)名為_,通常這種行為是占位的,表示這個(gè)參數(shù)沒(méi)有用到,但是又不能省略;

第二個(gè)參數(shù)是一個(gè)對(duì)象,這個(gè)對(duì)象里面有一個(gè)overwrite屬性,這個(gè)屬性就是overwrite問(wèn)題的答案;

他通過(guò)overwrite的值來(lái)判斷用戶(hù)是否選擇了覆蓋,如果選擇了覆蓋,就會(huì)跳過(guò)這個(gè)問(wèn)題;

否則的話就證明這個(gè)目錄下面存在文件,那么就會(huì)拋出一個(gè)錯(cuò)誤,這里拋出錯(cuò)誤是會(huì)終止整個(gè)命令的執(zhí)行的;

這一部分,在定義問(wèn)題數(shù)組的時(shí)候有做處理,使用try...catch來(lái)捕獲錯(cuò)誤,如果有錯(cuò)誤,就會(huì)使用return來(lái)終止整個(gè)命令的執(zhí)行;

try {
  result = await prompts([])
} catch (cancelled: any) {
  console.log(cancelled.message)
  return
}

packageName:包名

配置項(xiàng)如下:

var packageName = {
  type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
  name: 'packageName',
  message: reset('Package name:'),
  initial: () => toValidPackageName(getProjectName()),
  validate: (dir) =>
    isValidPackageName(dir) || 'Invalid package.json name'
}

這里的type配置項(xiàng)是一個(gè)函數(shù),里面通過(guò)isValidPackageName來(lái)判斷項(xiàng)目名稱(chēng)是否是一個(gè)合法的包名;

getProjectName在上面已經(jīng)介紹過(guò)了,這里就不再贅述;

isValidPackageName是用來(lái)判斷包名是否合法的,這個(gè)函數(shù)的實(shí)現(xiàn)如下:

function isValidPackageName(projectName: string) {
  return /^(?:@[a-z\d-*~][a-z\d-*._~]*/)?[a-z\d-~][a-z\d-._~]*$/.test(
    projectName
  )
}

validate用來(lái)驗(yàn)證用戶(hù)輸入的內(nèi)容是否合法,如果不合法,就會(huì)顯示Invalid package.json name;

framework:框架

配置項(xiàng)如下:

var framework =  {
  type:
    argTemplate && TEMPLATES.includes(argTemplate) ? null : 'select',
  name: 'framework',
  message:
    typeof argTemplate === 'string' && !TEMPLATES.includes(argTemplate)
      ? reset(
        `"${argTemplate}" isn't a valid template. Please choose from below: `
      )
      : reset('Select a framework:'),
  initial: 0,
  choices: FRAMEWORKS.map((framework) => {
    const frameworkColor = framework.color
    return {
      title: frameworkColor(framework.display || framework.name),
      value: framework
    }
  })
}

這里的就相對(duì)來(lái)說(shuō)復(fù)雜了點(diǎn),首先判斷了argTemplate是否存在,如果存在,就會(huì)判斷argTemplate是否是一個(gè)合法的模板;

TEMPLATES的定義是通過(guò)FRAMEWORKS來(lái)生成的:

const TEMPLATES = FRAMEWORKS.map((f) => {
    const variants = f.variants || [];
    const names = variants.map((v) => v.name);
    return names.length ? names : [f.name];
  }).reduce((a, b) => a.concat(b), [])

這里我將代碼拆分了一下,這樣看著會(huì)更清晰一點(diǎn),最后的reduce的作用應(yīng)該是對(duì)值進(jìn)行一個(gè)拷貝處理;

源碼里面的map返回的都是引用值,所以需要進(jìn)行拷貝(這是我猜測(cè)的),源碼如下:

const TEMPLATES = FRAMEWORKS.map(
  (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name]
).reduce((a, b) => a.concat(b), [])

FRAMEWORKS是寫(xiě)死的一個(gè)數(shù)組,代碼很長(zhǎng),就不貼出來(lái)了,這里就貼一下type的定義:

type Framework = {
  name: string
  display: string
  color: ColorFunc
  variants: FrameworkVariant[]
}
type FrameworkVariant = {
  name: string
  display: string
  color: ColorFunc
  customCommand?: string
}
  • name是框架的名稱(chēng);
  • display是顯示的名稱(chēng);
  • color是顏色;
  • variants是框架的語(yǔ)言,比如reacttypescriptjavascript兩種語(yǔ)言;
  • customCommand是自定義的命令,比如vuevue-cli就是自定義的命令;

分析到這里,再回頭看看framework的配置項(xiàng),就很好理解了,這里的choices就是通過(guò)FRAMEWORKS來(lái)生成的:

var framework = {
  choices: FRAMEWORKS.map((framework) => {
    const frameworkColor = framework.color
    return {
      title: frameworkColor(framework.display || framework.name),
      value: framework
    }
  })
}

choices是一個(gè)數(shù)組,用于表示typeselect時(shí)的選項(xiàng),數(shù)組的每一項(xiàng)都是一個(gè)對(duì)象,對(duì)象的title是顯示的名稱(chēng),value是選中的值;

上面的代碼就是用來(lái)生成choices的,frameworkColor是一個(gè)顏色函數(shù),用來(lái)給framework.display或者framework.name上色;

variant:語(yǔ)言

配置項(xiàng)如下:

var variant = {
  type: (framework: Framework) =>
    framework && framework.variants ? 'select' : null,
  name: 'variant',
  message: reset('Select a variant:'),
  choices: (framework: Framework) =>
    framework.variants.map((variant) => {
      const variantColor = variant.color
      return {
        title: variantColor(variant.display || variant.name),
        value: variant.name
      }
    })
}

這里的type是一個(gè)函數(shù),函數(shù)的第一個(gè)參數(shù)就是framework,這里的type是根據(jù)framework來(lái)判斷的,如果framework存在并且framework.variants存在,就讓用戶(hù)繼續(xù)這一個(gè)問(wèn)題。

通過(guò)之前的分析,這一塊應(yīng)該都能看明白,就繼續(xù)往下走;

獲取用戶(hù)輸入

接著往下走就是獲取用戶(hù)輸入了,用戶(hù)回答完所有問(wèn)題后,結(jié)果會(huì)返回到result中,可以用過(guò)解構(gòu)的方式來(lái)獲?。?/p>

const { framework, overwrite, packageName, variant } = result

清空目錄

接著就是對(duì)生成項(xiàng)目的位置進(jìn)行處理,根據(jù)上面分析的邏輯,會(huì)有目錄下有文件的情況,所以需要先清空目錄:

// 確定項(xiàng)目生成的目錄
const root = path.join(cwd, targetDir)
// 清空目錄
if (overwrite) {
  emptyDir(root)
} else if (!fs.existsSync(root)) {
  fs.mkdirSync(root, {recursive: true})
}

emptyDir是一個(gè)清空目錄的方法,fs.existsSync是用來(lái)判斷目錄是否存在的,如果不存在就創(chuàng)建一個(gè);

function emptyDir(dir: string) {
    // 如果目錄不存在,啥也不管
  if (!fs.existsSync(dir)) {
    return
  }
  // 讀取目錄下的所有文件
  for (const file of fs.readdirSync(dir)) {
      // 忽略 .git 的目錄
    if (file === '.git') {
      continue
    }
    // 刪除文件,如果是目錄就遞歸刪除
    fs.rmSync(path.resolve(dir, file), { recursive: true, force: true })
  }
}

existsSync第二個(gè)參數(shù)是一個(gè)對(duì)象,recursive表示是否遞歸創(chuàng)建目錄,如果目錄不存在,就會(huì)創(chuàng)建目錄,如果目錄存在,就會(huì)報(bào)錯(cuò);

生成項(xiàng)目

繼續(xù)往下走,就是生成項(xiàng)目相關(guān)的,最開(kāi)始肯定是確定項(xiàng)目的內(nèi)容。

確定項(xiàng)目模板

// 確定項(xiàng)目模板
const template: string = variant || framework?.name || argTemplate

這里的template就是項(xiàng)目的模板,如果用戶(hù)選擇了variant,那么就用variant,如果沒(méi)有選擇,就用framework,如果framework不存在,就用argTemplate

這些變量代表什么,從哪來(lái)的上面都有分析。

確定包管理器

const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)

這里的process.env.npm_config_user_agent并不是我們自己定義的,是npm自己定義的;

這個(gè)變量值是指的當(dāng)前運(yùn)行環(huán)境的包管理器,比如npmyarn等等,當(dāng)然這個(gè)值肯定沒(méi)我寫(xiě)的這么簡(jiǎn)單;

通過(guò)debug可以看到我的值是pnpm/7.17.0 npm/? node/v14.19.2 win32 x64,每個(gè)人的值根據(jù)環(huán)境的不同而不同;

pkgFromUserAgent是一個(gè)解析userAgent的方法,大白話就是解析包管理器的名稱(chēng)和版本號(hào);

例如{name: 'npm', version: '7.17.0'},代碼如下:

function pkgFromUserAgent(userAgent: string | undefined) {
  if (!userAgent) return undefined
  const pkgSpec = userAgent.split(' ')[0]
  const pkgSpecArr = pkgSpec.split('/')
  return {
    name: pkgSpecArr[0],
    version: pkgSpecArr[1]
  }
}

這個(gè)代碼也沒(méi)那么高深,就是解析字符串,然后返回一個(gè)對(duì)象,給你寫(xiě)也一定可以寫(xiě)出來(lái)的;

后面兩段代碼就是正式確定包管理器的名稱(chēng)和版本號(hào)了,代碼如下:

const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
const isYarn1 = pkgManager === 'yarn' && pkgInfo?.version.startsWith('1.')

yarn的版本如果是1.x后面會(huì)有一些特殊處理,所以會(huì)有isYarn1這個(gè)變量;

接著就是確定包管理器的命令了,代碼如下:

 const { customCommand } =
    FRAMEWORKS.flatMap((f) => f.variants).find((v) => v.name === template) ?? {}

這一段是用來(lái)確定部分模板的包管理器命令的,比如vue-cli,vue-cli的包管理器命令是vue,會(huì)有不一樣的命令;

if (customCommand) {
  const fullCustomCommand = customCommand
    .replace('TARGET_DIR', targetDir)
    .replace(/^npm create/, `${pkgManager} create`)
    // Only Yarn 1.x doesn't support `@version` in the `create` command
    .replace('@latest', () => (isYarn1 ? '' : '@latest'))
    .replace(/^npm exec/, () => {
      // Prefer `pnpm dlx` or `yarn dlx`
      if (pkgManager === 'pnpm') {
        return 'pnpm dlx'
      }
      if (pkgManager === 'yarn' && !isYarn1) {
        return 'yarn dlx'
      }
      // Use `npm exec` in all other cases,
      // including Yarn 1.x and other custom npm clients.
      return 'npm exec'
    })
  const [command, ...args] = fullCustomCommand.split(' ')
  const {status} = spawn.sync(command, args, {
    stdio: 'inherit'
  })
  process.exit(status ?? 0)
}

這里的處理代碼比較多,但是也沒(méi)什么好看的,就是各種替換字符串,然后生成最終的命令;

正式生成項(xiàng)目

接下來(lái)就是重點(diǎn)了,首先確定模板的位置,代碼如下:

const templateDir = path.resolve(
  fileURLToPath(import.meta.url),
  '../..',
  `template-${template}`
)

這里的import.meta.url是當(dāng)前ES模塊的絕對(duì)路徑,這里是一個(gè)知識(shí)點(diǎn)。

import大家都知道是用來(lái)導(dǎo)入模塊的,但是import.meta是什么呢?

import.meta是一個(gè)對(duì)象,它的屬性和方法提供了有關(guān)模塊的信息,比如url就是當(dāng)前模塊的絕對(duì)路徑;

同時(shí)他還允許在模塊中添加自定義的屬性,比如import.meta.foo = 'bar',這樣就可以在模塊中使用import.meta.foo了;

所以我們?cè)?code>vite項(xiàng)目中可以使用import.meta.env來(lái)獲取環(huán)境變量,比如import.meta.env.MODE就是當(dāng)前的模式;

點(diǎn)到為止,我們繼續(xù)看代碼,這一段就是確定模板的位置,應(yīng)該都看的懂;

后面就是讀取模板文件,然后生成項(xiàng)目了,代碼如下:

const files = fs.readdirSync(templateDir)
// package.json 不需要寫(xiě)進(jìn)去
for (const file of files.filter((f) => f !== 'package.json')) {
  write(file)
}

這里的write函數(shù)就是用來(lái)生成項(xiàng)目的,代碼如下:

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)
  }
}

根據(jù)上面的邏輯這個(gè)分析直接簡(jiǎn)化為:

const write = (file: string) => {
  const targetPath = path.join(root, file)
  copy(path.join(templateDir, file), targetPath)
}

這個(gè)沒(méi)啥好說(shuō)的,然后就到了copy函數(shù)的分析了,代碼如下:

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

這里的copy函數(shù)就是用來(lái)復(fù)制文件的,如果是文件夾就調(diào)用copyDir函數(shù),代碼如下:

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)
  }
}

這里的fs.mkdirSync函數(shù)就是用來(lái)創(chuàng)建文件夾的,recursive參數(shù)表示如果父級(jí)文件夾不存在就創(chuàng)建父級(jí)文件夾;

這里的fs.readdirSync函數(shù)就是用來(lái)讀取文件夾的,返回一個(gè)數(shù)組,數(shù)組中的每一項(xiàng)就是文件夾中的文件名;

最后通過(guò)遞歸調(diào)用copy函數(shù)來(lái)復(fù)制文件夾中的文件;

創(chuàng)建package.json

接下來(lái)是對(duì)package.json文件的單獨(dú)處理,代碼如下:

// 獲取模板中的 package.json
const pkg = JSON.parse(
  fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8')
)
// 修改 package.json 中的 name 值
pkg.name = packageName || getProjectName()
// 寫(xiě)入 package.json
write('package.json', JSON.stringify(pkg, null, 2))

這里的pkg就是模板中的package.json文件,然后修改name字段,最后寫(xiě)入到項(xiàng)目中;

之前不復(fù)制package.json是因?yàn)檫@里會(huì)修改name字段,如果復(fù)制了你的項(xiàng)目的name屬性就不正確。

完成

console.log(`\nDone. Now run:\n`)
if (root !== cwd) {
  console.log(`  cd ${path.relative(cwd, root)}`)
}
switch (pkgManager) {
  case 'yarn':
    console.log('  yarn')
    console.log('  yarn dev')
    break
  default:
    console.log(`  ${pkgManager} install`)
    console.log(`  ${pkgManager} run dev`)
    break
}
console.log()

最后就是一些提示信息,如果你的項(xiàng)目不在當(dāng)前目錄下,就會(huì)提示你cd到項(xiàng)目目錄下,然后根據(jù)你的包管理器來(lái)提示你安裝依賴(lài)和啟動(dòng)項(xiàng)目。

總結(jié)

整體下來(lái)這個(gè)腳手架的實(shí)現(xiàn)還是比較簡(jiǎn)單的,整體非常清晰:

  • 通過(guò)minimist來(lái)解析命令行參數(shù);
  • 通過(guò)prompts來(lái)交互式的獲取用戶(hù)輸入;
  • 確認(rèn)用戶(hù)輸入的信息,整合項(xiàng)目信息;
  • 通過(guò)nodefs模塊來(lái)創(chuàng)建項(xiàng)目;
  • 最后提示用戶(hù)如何啟動(dòng)項(xiàng)目。

代碼不多,但是整體走下來(lái)還是有很多細(xì)節(jié)的,例如:

  • 以后寫(xiě)node項(xiàng)目的時(shí)候知道怎么獲取命令行參數(shù);
  • 用戶(hù)命令行的交互式輸入,里面用戶(hù)體驗(yàn)是非常好的,這個(gè)可以在很多地方是做為參考;
  • fs模塊的使用,這個(gè)模塊是node中非常重要的模塊;
  • node中的path模塊,這個(gè)模塊也是非常重要的,很多地方都會(huì)用到;
  • import的知識(shí)點(diǎn),真的學(xué)到了。

以上就是create vite 實(shí)例源碼解析的詳細(xì)內(nèi)容,更多關(guān)于create vite源碼解析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue3中的onMounted詳解與使用方法詳解

    Vue3中的onMounted詳解與使用方法詳解

    這篇文章主要介紹了Vue3中的onMounted生命周期鉤子,它在組件掛載到DOM后執(zhí)行代碼,onMounted只調(diào)用一次,適合進(jìn)行異步操作、DOM操作和事件監(jiān)聽(tīng),在使用時(shí)需要注意組件銷(xiāo)毀和響應(yīng)式數(shù)據(jù)的管理,需要的朋友可以參考下
    2024-11-11
  • 使用mockjs如何生成隨機(jī)數(shù)據(jù)

    使用mockjs如何生成隨機(jī)數(shù)據(jù)

    Mockjs是一個(gè)用于生成隨機(jī)數(shù)據(jù)和攔截Ajax請(qǐng)求的庫(kù),可以與Vue和Axios結(jié)合使用,提高前端開(kāi)發(fā)效率,通過(guò)在項(xiàng)目中引入Mock.js文件,可以模擬后端API,攔截Ajax請(qǐng)求并返回自定義響應(yīng),這種方法適用于在后端尚未開(kāi)發(fā)完成時(shí)的前端開(kāi)發(fā)測(cè)試
    2024-10-10
  • vue用BMap百度地圖實(shí)現(xiàn)即時(shí)搜索功能

    vue用BMap百度地圖實(shí)現(xiàn)即時(shí)搜索功能

    這篇文章主要為大家詳細(xì)介紹了vue用BMap百度地圖實(shí)現(xiàn)即時(shí)搜索功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 從0搭建vue-cli4腳手架

    從0搭建vue-cli4腳手架

    這篇文章主要介紹了從0搭建vue-cli4腳手架,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Vue與Nuxt的區(qū)別及使用說(shuō)明

    Vue與Nuxt的區(qū)別及使用說(shuō)明

    這篇文章主要介紹了Vue與Nuxt的區(qū)別及使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • vue版日歷組件的實(shí)現(xiàn)方法

    vue版日歷組件的實(shí)現(xiàn)方法

    這篇文章主要為大家詳細(xì)介紹了vue版日歷組件的實(shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • ejsExcel模板在Vue.js項(xiàng)目中的實(shí)際運(yùn)用

    ejsExcel模板在Vue.js項(xiàng)目中的實(shí)際運(yùn)用

    這篇文章主要介紹了ejsExcel模板在Vue.js項(xiàng)目中的實(shí)際運(yùn)用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • 利用Vue實(shí)現(xiàn)一個(gè)markdown編輯器實(shí)例代碼

    利用Vue實(shí)現(xiàn)一個(gè)markdown編輯器實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于如何利用Vue實(shí)現(xiàn)一個(gè)markdown編輯器的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • element?tooltip的使用

    element?tooltip的使用

    本文主要介紹了element?tooltip的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Vue利用廣度優(yōu)先搜索實(shí)現(xiàn)watch

    Vue利用廣度優(yōu)先搜索實(shí)現(xiàn)watch

    這篇文章主要為大家學(xué)習(xí)介紹了Vue如何利用廣度優(yōu)先搜索實(shí)現(xiàn)watch(有意思),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-08-08

最新評(píng)論

军事| 兴义市| 虎林市| 仁寿县| 凉城县| 天祝| 万年县| 平山县| 固始县| 元江| 平湖市| 上林县| 高阳县| 东台市| 定安县| 澜沧| 宣武区| 泰宁县| 云龙县| 兴隆县| 本溪| 克东县| 吴堡县| 奎屯市| 玉田县| 盘锦市| 井陉县| 安多县| 墨脱县| 崇信县| 蒲城县| 前郭尔| 塔河县| 琼结县| 开封市| 乐陵市| 农安县| 乌什县| 旌德县| 达拉特旗| 萨迦县|