国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - 編程技術 - 「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

2021-12-19 22:43前端歷劫之路maomin9761 編程技術

最近一直在搞Strve.js生態,在自己搗鼓框架的同時也學到了很多東西。所以就本篇文章給大家介紹一種更加方便靈活的命令行腳手架工具,以及如何發布到NPM上。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

前言

為什么要寫這篇文章呢?是因為最近一直在搞Strve.js生態,在自己搗鼓框架的同時也學到了很多東西。所以就本篇文章給大家介紹一種更加方便靈活的命令行腳手架工具,以及如何發布到NPM上。

之前,我也寫過類似的開發命令行工具的文章,但是核心思想都是通過代碼遠程拉取Git倉庫中的項目模板代碼。有時候會因為網速的原因導致拉取失敗,進而會初始化項目失敗。

那么,有沒有比這個更好的方案呢?那么本篇就來了。

最近,使用Vite工具開發了很多項目。不得不佩服尤老師驚人的代碼能力,創建了這么好的開發工具,開發體驗非常絲滑。尤其是你剛初始化項目時,只需要執行一行命令,也不用全局安裝什么工具。然后,自定義選擇需要的模板進行初始化項目,就大功告成了!這種操作著實把我驚到了!我在想,如果我把create-vite的這種思路應用到我自己的腳手架工具中是不是很Nice!

實戰

所以,二話不說,就抓緊打開ViteGitHub地址。

https://github.com/vitejs

找了大半天,終于找到了命令行工具核心代碼。

https://github.com/vitejs/vite/tree/main/packages/create-vite

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

映入眼簾的是很多以template-開頭的文件夾,打開幾個都看了一下,都是框架項目模板。那么,可以先放在一邊。

下一步,我們就打開index.js文件看下什么內容。我列下代碼,大家可以簡單看一下,不用深究。

  1. #!/usr/bin/env node
  2. // @ts-check
  3. const fs = require('fs')
  4. const path = require('path')
  5. // Avoids autoconversion to number of the project name by defining that the args
  6. // non associated with an option ( _ ) needs to be parsed as a string. See #4606
  7. const argv = require('minimist')(process.argv.slice(2), { string: ['_'] })
  8. // eslint-disable-next-line node/no-restricted-require
  9. const prompts = require('prompts')
  10. const {
  11. yellow,
  12. green,
  13. cyan,
  14. blue,
  15. magenta,
  16. lightRed,
  17. red
  18. } = require('kolorist')
  19. const cwd = process.cwd()
  20. const FRAMEWORKS = [
  21. {
  22. name: 'vanilla',
  23. color: yellow,
  24. variants: [
  25. {
  26. name: 'vanilla',
  27. display: 'JavaScript',
  28. color: yellow
  29. },
  30. {
  31. name: 'vanilla-ts',
  32. display: 'TypeScript',
  33. color: blue
  34. }
  35. ]
  36. },
  37. {
  38. name: 'vue',
  39. color: green,
  40. variants: [
  41. {
  42. name: 'vue',
  43. display: 'JavaScript',
  44. color: yellow
  45. },
  46. {
  47. name: 'vue-ts',
  48. display: 'TypeScript',
  49. color: blue
  50. }
  51. ]
  52. },
  53. {
  54. name: 'react',
  55. color: cyan,
  56. variants: [
  57. {
  58. name: 'react',
  59. display: 'JavaScript',
  60. color: yellow
  61. },
  62. {
  63. name: 'react-ts',
  64. display: 'TypeScript',
  65. color: blue
  66. }
  67. ]
  68. },
  69. {
  70. name: 'preact',
  71. color: magenta,
  72. variants: [
  73. {
  74. name: 'preact',
  75. display: 'JavaScript',
  76. color: yellow
  77. },
  78. {
  79. name: 'preact-ts',
  80. display: 'TypeScript',
  81. color: blue
  82. }
  83. ]
  84. },
  85. {
  86. name: 'lit',
  87. color: lightRed,
  88. variants: [
  89. {
  90. name: 'lit',
  91. display: 'JavaScript',
  92. color: yellow
  93. },
  94. {
  95. name: 'lit-ts',
  96. display: 'TypeScript',
  97. color: blue
  98. }
  99. ]
  100. },
  101. {
  102. name: 'svelte',
  103. color: red,
  104. variants: [
  105. {
  106. name: 'svelte',
  107. display: 'JavaScript',
  108. color: yellow
  109. },
  110. {
  111. name: 'svelte-ts',
  112. display: 'TypeScript',
  113. color: blue
  114. }
  115. ]
  116. }
  117. ]
  118. const TEMPLATES = FRAMEWORKS.map(
  119. (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name]
  120. ).reduce((a, b) => a.concat(b), [])
  121. const renameFiles = {
  122. _gitignore: '.gitignore'
  123. }
  124. async function init() {
  125. let targetDir = argv._[0]
  126. let template = argv.template || argv.t
  127. const defaultProjectName = !targetDir ? 'vite-project' : targetDir
  128. let result = {}
  129. try {
  130. result = await prompts(
  131. [
  132. {
  133. type: targetDir ? null : 'text',
  134. name: 'projectName',
  135. message: 'Project name:',
  136. initial: defaultProjectName,
  137. onState: (state) =>
  138. (targetDir = state.value.trim() || defaultProjectName)
  139. },
  140. {
  141. type: () =>
  142. !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
  143. name: 'overwrite',
  144. message: () =>
  145. (targetDir === '.'
  146. ? 'Current directory'
  147. : `Target directory "${targetDir}"`) +
  148. ` is not empty. Remove existing files and continue?`
  149. },
  150. {
  151. type: (_, { overwrite } = {}) => {
  152. if (overwrite === false) {
  153. throw new Error(red('?') + ' Operation cancelled')
  154. }
  155. return null
  156. },
  157. name: 'overwriteChecker'
  158. },
  159. {
  160. type: () => (isValidPackageName(targetDir) ? null : 'text'),
  161. name: 'packageName',
  162. message: 'Package name:',
  163. initial: () => toValidPackageName(targetDir),
  164. validate: (dir) =>
  165. isValidPackageName(dir) || 'Invalid package.json name'
  166. },
  167. {
  168. type: template && TEMPLATES.includes(template) ? null : 'select',
  169. name: 'framework',
  170. message:
  171. typeof template === 'string' && !TEMPLATES.includes(template)
  172. ? `"${template}" isn't a valid template. Please choose from below: `
  173. : 'Select a framework:',
  174. initial: 0,
  175. choices: FRAMEWORKS.map((framework) => {
  176. const frameworkColor = framework.color
  177. return {
  178. title: frameworkColor(framework.name),
  179. value: framework
  180. }
  181. })
  182. },
  183. {
  184. type: (framework) =>
  185. framework && framework.variants ? 'select' : null,
  186. name: 'variant',
  187. message: 'Select a variant:',
  188. // @ts-ignore
  189. choices: (framework) =>
  190. framework.variants.map((variant) => {
  191. const variantColor = variant.color
  192. return {
  193. title: variantColor(variant.name),
  194. value: variant.name
  195. }
  196. })
  197. }
  198. ],
  199. {
  200. onCancel: () => {
  201. throw new Error(red('?') + ' Operation cancelled')
  202. }
  203. }
  204. )
  205. } catch (cancelled) {
  206. console.log(cancelled.message)
  207. return
  208. }
  209. // user choice associated with prompts
  210. const { framework, overwrite, packageName, variant } = result
  211. const root = path.join(cwd, targetDir)
  212. if (overwrite) {
  213. emptyDir(root)
  214. } else if (!fs.existsSync(root)) {
  215. fs.mkdirSync(root)
  216. }
  217. // determine template
  218. template = variant || framework || template
  219. console.log(`\nScaffolding project in ${root}...`)
  220. const templateDir = path.join(__dirname, `template-${template}`)
  221. const write = (file, content) => {
  222. const targetPath = renameFiles[file]
  223. ? path.join(root, renameFiles[file])
  224. : path.join(root, file)
  225. if (content) {
  226. fs.writeFileSync(targetPath, content)
  227. } else {
  228. copy(path.join(templateDir, file), targetPath)
  229. }
  230. }
  231. const files = fs.readdirSync(templateDir)
  232. for (const file of files.filter((f) => f !== 'package.json')) {
  233. write(file)
  234. }
  235. const pkg = require(path.join(templateDir, `package.json`))
  236. pkg.name = packageName || targetDir
  237. write('package.json', JSON.stringify(pkg, null, 2))
  238. const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent)
  239. const pkgManager = pkgInfo ? pkgInfo.name : 'npm'
  240. console.log(`\nDone. Now run:\n`)
  241. if (root !== cwd) {
  242. console.log(` cd ${path.relative(cwd, root)}`)
  243. }
  244. switch (pkgManager) {
  245. case 'yarn':
  246. console.log(' yarn')
  247. console.log(' yarn dev')
  248. break
  249. default:
  250. console.log(` ${pkgManager} install`)
  251. console.log(` ${pkgManager} run dev`)
  252. break
  253. }
  254. console.log()
  255. }
  256. function copy(src, dest) {
  257. const stat = fs.statSync(src)
  258. if (stat.isDirectory()) {
  259. copyDir(src, dest)
  260. } else {
  261. fs.copyFileSync(src, dest)
  262. }
  263. }
  264. function isValidPackageName(projectName) {
  265. return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
  266. projectName
  267. )
  268. }
  269. function toValidPackageName(projectName) {
  270. return projectName
  271. .trim()
  272. .toLowerCase()
  273. .replace(/\s+/g, '-')
  274. .replace(/^[._]/, '')
  275. .replace(/[^a-z0-9-~]+/g, '-')
  276. }
  277. function copyDir(srcDir, destDir) {
  278. fs.mkdirSync(destDir, { recursive: true })
  279. for (const file of fs.readdirSync(srcDir)) {
  280. const srcFile = path.resolve(srcDir, file)
  281. const destFile = path.resolve(destDir, file)
  282. copy(srcFile, destFile)
  283. }
  284. }
  285. function isEmpty(path) {
  286. return fs.readdirSync(path).length === 0
  287. }
  288. function emptyDir(dir) {
  289. if (!fs.existsSync(dir)) {
  290. return
  291. }
  292. for (const file of fs.readdirSync(dir)) {
  293. const abs = path.resolve(dir, file)
  294. // baseline is Node 12 so can't use rmSync :(
  295. if (fs.lstatSync(abs).isDirectory()) {
  296. emptyDir(abs)
  297. fs.rmdirSync(abs)
  298. } else {
  299. fs.unlinkSync(abs)
  300. }
  301. }
  302. }
  303. /**
  304. * @param {string | undefined} userAgent process.env.npm_config_user_agent
  305. * @returns object | undefined
  306. */
  307. function pkgFromUserAgent(userAgent) {
  308. if (!userAgent) return undefined
  309. const pkgSpec = userAgent.split(' ')[0]
  310. const pkgSpecArr = pkgSpec.split('/')
  311. return {
  312. name: pkgSpecArr[0],
  313. version: pkgSpecArr[1]
  314. }
  315. }
  316. init().catch((e) => {
  317. console.error(e)
  318. })

看到上面這么多代碼是不是不想繼續閱讀下去了?不要慌!我們其實就用到里面幾個地方,可以放心的繼續閱讀下去。

這些代碼算是Create Vite核心代碼了,我們會看到常量FRAMEWORKS定義了一個數組對象,另外數組對象中都是一些我們初始化項目時需要選擇安裝的框架。所以,我們可以先ViteGithub項目Clone下來,試試效果。

然后,將項目Clone下來之后,我們找到/packages/create-vite這個文件夾,我們現在就只關注這個文件夾。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

我用的Yarn依賴管理工具,所以我首先使用命令初始化依賴。

  1. yarn

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

然后,我們可以先打開根目錄下的package.json文件,會發現有如下命令。

  1. {
  2. "bin": {
  3. "create-vite": "index.js",
  4. "cva": "index.js"
  5. }
  6. }

我們可以在這里起一個自己模板的名字,比如我們就叫demo,

  1. {
  2. "bin": {
  3. "create-demo": "index.js",
  4. "cvd": "index.js"
  5. }
  6. }

然后,我們先在這里使用yarn link命令來將此命令在本地可以運行。

然后再運行create-demo命令·。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

會顯示一些交互文本,會發現非常熟悉,這正是我們創建Vite項目時所看到的。我們在前面說到我們想實現一個屬于自己的項目模板,現在我們也找到了核心。所以就開始干起來吧!

我們會看到在根目錄下有很多template-開頭的文件夾,我們打開一個看一下。比如template-vue。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

原來模板都在這!但是這些模板文件都是以template-開頭,是不是有什么約定?所以,我們打算回頭再去看下index.js文件。

  1. // determine template
  2. template = variant || framework || template
  3. console.log(`\nScaffolding project in ${root}...`)
  4. const templateDir = path.join(__dirname, `template-${template}`)

果真,所以模板都必須以template-開頭。

那么,我們就在根目錄下面建一個template-demo文件夾,里面再放一個index.js文件,作為示例模板。

我們在執行初始化項目時發現,需要選擇對應的模板,那么這些選項是從哪里來的呢?我們決定再回去看下根目錄下的index.js文件。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

會發現有這么一個數組,里面正是我們要選擇的框架模板。

  1. const FRAMEWORKS = [
  2. {
  3. name: 'vanilla',
  4. color: yellow,
  5. variants: [
  6. {
  7. name: 'vanilla',
  8. display: 'JavaScript',
  9. color: yellow
  10. },
  11. {
  12. name: 'vanilla-ts',
  13. display: 'TypeScript',
  14. color: blue
  15. }
  16. ]
  17. },
  18. {
  19. name: 'vue',
  20. color: green,
  21. variants: [
  22. {
  23. name: 'vue',
  24. display: 'JavaScript',
  25. color: yellow
  26. },
  27. {
  28. name: 'vue-ts',
  29. display: 'TypeScript',
  30. color: blue
  31. }
  32. ]
  33. },
  34. {
  35. name: 'react',
  36. color: cyan,
  37. variants: [
  38. {
  39. name: 'react',
  40. display: 'JavaScript',
  41. color: yellow
  42. },
  43. {
  44. name: 'react-ts',
  45. display: 'TypeScript',
  46. color: blue
  47. }
  48. ]
  49. },
  50. {
  51. name: 'preact',
  52. color: magenta,
  53. variants: [
  54. {
  55. name: 'preact',
  56. display: 'JavaScript',
  57. color: yellow
  58. },
  59. {
  60. name: 'preact-ts',
  61. display: 'TypeScript',
  62. color: blue
  63. }
  64. ]
  65. },
  66. {
  67. name: 'lit',
  68. color: lightRed,
  69. variants: [
  70. {
  71. name: 'lit',
  72. display: 'JavaScript',
  73. color: yellow
  74. },
  75. {
  76. name: 'lit-ts',
  77. display: 'TypeScript',
  78. color: blue
  79. }
  80. ]
  81. },
  82. {
  83. name: 'svelte',
  84. color: red,
  85. variants: [
  86. {
  87. name: 'svelte',
  88. display: 'JavaScript',
  89. color: yellow
  90. },
  91. {
  92. name: 'svelte-ts',
  93. display: 'TypeScript',
  94. color: blue
  95. }
  96. ]
  97. }
  98. ]

所以,可以在后面數組后面再添加一個對象。

  1. {
  2. name: 'demo',
  3. color: red,
  4. variants: [
  5. {
  6. name: 'demo',
  7. display: 'JavaScript',
  8. color: yellow
  9. }
  10. ]
  11. }

好,你會發現我這里會有個color屬性,并且有類似顏色值的屬性值,這是依賴kolorist導出的常量。kolorist是一個將顏色放入標準輸入/標準輸出的小庫。我們在之前那些模板交互文本會看到它們顯示不同顏色,這正是它的功勞。

  1. const {
  2. yellow,
  3. green,
  4. cyan,
  5. blue,
  6. magenta,
  7. lightRed,
  8. red
  9. } = require('kolorist')

我們,也將模板對象添加到數組里了,那么下一步我們執行命令看下效果。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

會發現多了一個demo模板,這正是我們想要的。

我們繼續執行下去。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

我們會看到根目錄下已經成功創建了demo1文件夾,并且里面正是我們想要的demo模板。

上圖顯示的Error,是因為我沒有在demo模板上創建package.json文件,所以這里可以忽略。你可以在自己的模板里創建一個package.json文件。

雖然,我們成功在本地創建了自己的一個模板,但是,我們只能本地創建。也就是說你換臺電腦,就沒有辦法執行這個創建模板的命令。

所以,我們要想辦法去發布到云端,這里我們發布到NPM上。

首先,我們重新新建一個項目目錄,將其他模板刪除,只保留我們自己的模板。另外,將數組中的其他模板對象刪除,保留一個自己的模板。

我以自己的模板create-strve-app為例。

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

然后,我們打開package.json文件,需要修改一些信息。

以create-strve-app為例:

  1. {
  2. "name": "create-strve-app",
  3. "version": "1.3.3",
  4. "license": "MIT",
  5. "author": "maomincoding",
  6. "bin": {
  7. "create-strve-app": "index.js",
  8. "cs-app": "index.js"
  9. },
  10. "files": [
  11. "index.js",
  12. "template-*"
  13. ],
  14. "main": "index.js",
  15. "private": false,
  16. "keywords": ["strve","strvejs","dom","mvvm","virtual dom","html","template","string","create-strve","create-strve-app"],
  17. "engines": {
  18. "node": ">=12.0.0"
  19. },
  20. "repository": {
  21. "type": "git",
  22. "url": "git+https://github.com/maomincoding/create-strve-app.git"
  23. },
  24. "bugs": {
  25. "url": "https://github.com/maomincoding/create-strve-app/issues"
  26. },
  27. "homepage": "https://github.com/maomincoding/create-strve-app#readme",
  28. "dependencies": {
  29. "kolorist": "^1.5.0",
  30. "minimist": "^1.2.5",
  31. "prompts": "^2.4.2"
  32. }
  33. }

注意,每次發布前,version字段必須與之前不同,否則發布失敗。

最后,我們依次運行如下命令。

切換到npm源

  1. npm config set registry=https://registry.npmjs.org

登錄NPM(如果已登錄,可忽略此步)

  1. npm login

發布NPM

  1. npm publish

我們可以登錄到NPM(https://www.npmjs.com/)

查看已經發布成功!

「Create-?」每個前端開發者都可以擁有屬于自己的命令行腳手架

以后,我們就可以直接運行命令下載自定義模板。這在我們重復使用模板時非常有用,不僅可以提升效率,而且還可以避免犯很多不必要的錯誤。

結語

另外,此篇舉例的 Create Strve App 是一套快速搭建Strve.js項目的命令行工具。如果你對此感興趣,可以訪問以下地址查看源碼:

https://github.com/maomincoding/create-strve-app

熬夜奮戰二個多月,Strve.js生態初步已經建成,以下是Strve.js 最新文檔地址,歡迎瀏覽。

https://maomincoding.github.io/strvejs-doc/

原文鏈接:https://mp.weixin.qq.com/s/2ndyBAQZRro4-9NonHPP2Q

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日韩在线欧美 | 欧美日韩一区在线 | 老司机午夜影院 | 在线观看亚洲精品 | 午夜免费视频网站 | 亚洲伊人成人 | 国产区免费在线观看 | 亚洲国产激情 | 日韩中文字幕视频在线观看 | 一区二区三区免费看 | 日韩精品成人 | 精品第一页 | 国产一区 | 羞羞动漫网 | av在线免费观看网址 | 91亚洲国产 | 日韩三级电影在线免费观看 | 九九在线视频 | 欧美影视一区二区三区 | 日韩免费av | 日韩一区二区三区在线播放 | jiuse九色| 制服诱惑一区二区 | 欧美在线综合 | 亚洲专区中文字幕 | 亚洲欧美激情精品一区二区 | 日日操天天爽 | 日韩国产中文字幕 | 日韩成人一级片 | 成人免费黄色毛片 | 久草成人 | 91亚洲国产成人久久精品网站 | 国产精品久久久久久久久费观看 | 国产伦精品一区二区三区高清 | 久久久久中文字幕 | 国产一区二区三区在线看 | 激情欧美日韩一区二区 | 日韩欧美精品一区二区 | 国产高清精品一区 | 天堂av一区二区 | 日韩中文视频 |