欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從零搭一個自用的前端腳手架的方法步驟

 更新時間:2019年09月23日 09:40:53   作者:ZoeLee  
這篇文章主要介紹了從零搭一個自用的前端腳手架的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

為什么要弄個腳手架

對于我個人,經(jīng)常寫些demo,或者寫一個新項目的時候,要么就是把以前的項目模板復(fù)制一份,要么就是重新搭建一份,顯得比較麻煩,浪費時間,所以就有了搭建一個能滿足自己需要的腳手架。

腳手架的效果

這是一個基本的腳手架,init一個項目,輸入項目名稱,版本號等信息,然后從git倉庫拷貝一份自己需要的項目模板。類似vue的vue-cli或者react的create-react-app,只是這個比較簡單.

基本思路參考下圖


這部分參考了掘金@張國鈺大佬的思路.

項目結(jié)構(gòu)


主要3個,一個bin文件夾,放執(zhí)行命令的入口文件

lib文件夾,放項目的主要文件,package.json不多說

這項目主要用到的幾個包

  • commander: 命令行工具
  • download-git-repo: 用來下載遠(yuǎn)程模板
  • ora: 顯示loading動畫
  • chalk: 修改控制臺輸出內(nèi)容樣式
  • log-symbols: 顯示出 √ 或 × 等的圖標(biāo)
  • inquirer.js:命令交互
  • metalsmith:處理項目模板
  • handlebars:模板引擎

使用commander.js命令行工具

修改package.json的bin執(zhí)行入口,

"bin": {
  "lz": "./bin/www"
 },

"lz"這個命令可以自己選擇,然后在bin文件加創(chuàng)建名為www的文件,

#! /usr/bin/env node
require('../lib/index.js');

其中

#! /usr/bin/env node

不能少,這個主要指定當(dāng)前腳本由node.js進(jìn)行解析

在lib創(chuàng)建一個index.js文件,

const program = require('commander')

program.version('1.0.0')
  .usage('<command> [項目名稱]')
  .command('init', '創(chuàng)建新項目')
 .parse(process.argv);

為方便測試,先鏈接到全局環(huán)境

npm link

執(zhí)行下命令感受下

lz init hello

正常來說,應(yīng)該就報錯了,錯誤堆棧大概就是確實www-init文件,

這是因為
commander支持git風(fēng)格的子命令處理,可以根據(jù)子命令自動引導(dǎo)到以特定格式命名的命令執(zhí)行文件,文件名的格式是[command]-[subcommand],例如:

macaw hello => macaw-hello
macaw init => macaw-init

所以我們 執(zhí)行www文件的init,所以要在bin創(chuàng)建一個www-init文件,在lib創(chuàng)建個init.js文件
www-init

#! /usr/bin/env node
require('../lib/init.js');

init.js 完整代碼

const program = require('commander')
const path = require('path')
const fs = require('fs')
const glob = require('glob') // npm i glob -D
const download = require('../lib/download.js')
const inquirer = require('inquirer')
const chalk = require('chalk')
const generator = require('../lib/generator')
const logSymbols = require("log-symbols");

 
program.usage('<project-name>')

// 根據(jù)輸入,獲取項目名稱
let projectName = process.argv[2];

if (!projectName) { // project-name 必填
 // 相當(dāng)于執(zhí)行命令的--help選項,顯示help信息,這是commander內(nèi)置的一個命令選項
 program.help()
 return
}

const list = glob.sync('*') // 遍歷當(dāng)前目錄

let next = undefined;

let rootName = path.basename(process.cwd());
if (list.length) { // 如果當(dāng)前目錄不為空
 if (list.some(n => {
  const fileName = path.resolve(process.cwd(), n);
  const isDir = fs.statSync(fileName).isDirectory();
  return projectName === n && isDir
 })) {
  console.log(`項目${projectName}已經(jīng)存在`);
  return;
 }
 rootName = projectName;
 next = Promise.resolve(projectName);
} else if (rootName === projectName) {
 rootName = '.';
 next = inquirer.prompt([
  {
   name: 'buildInCurrent',
   message: '當(dāng)前目錄為空,且目錄名稱和項目名稱相同,是否直接在當(dāng)前目錄下創(chuàng)建新項目?',
   type: 'confirm',
   default: true
  }
 ]).then(answer => {
  return Promise.resolve(answer.buildInCurrent ? '.' : projectName)
 })
} else {
 rootName = projectName;
 next = Promise.resolve(projectName)
}

next && go()

function go() {
 next
  .then(projectRoot => {
   if (projectRoot !== '.') {
    fs.mkdirSync(projectRoot)
   }
   return download(projectRoot).then(target => {
    return {
     name: projectRoot,
     root: projectRoot,
     downloadTemp: target
    }
   })
  })
  .then(context => {
   return inquirer.prompt([
    {
     name: 'projectName',
     message: '項目的名稱',
     default: context.name
    }, {
     name: 'projectVersion',
     message: '項目的版本號',
     default: '1.0.0'
    }, {
     name: 'projectDescription',
     message: '項目的簡介',
     default: `A project named ${context.name}`
    }
   ]).then(answers => {
    return {
     ...context,
     metadata: {
      ...answers
     }
    }
   })
  })
  .then(context => {
   //刪除臨時文件夾,將文件移動到目標(biāo)目錄下
   return generator(context);
  })
  .then(context => {
   // 成功用綠色顯示,給出積極的反饋
   console.log(logSymbols.success, chalk.green('創(chuàng)建成功:)'))
   console.log(chalk.green('cd ' + context.root + '\nnpm install\nnpm run dev'))
  })
  .catch(err => {
   // 失敗了用紅色,增強提示
   console.log(err);
   console.error(logSymbols.error, chalk.red(`創(chuàng)建失?。?{err.message}`))
  })
}

init.js都做了什么呢?

首先,獲得 init 后面輸入的參數(shù),作為項目名稱,當(dāng)然判斷這個項目名稱是否存在,然后進(jìn)行對應(yīng)的邏輯操作,通過download-git-repo工具,下載倉庫的模板,然后通過inquirer.js 處理命令行交互,獲得輸入的名稱,版本號能信息,最后在根據(jù)這些信息,處理模板文件。

用download-git-repo下載模板文件

在lib下創(chuàng)建download.js文件

const download = require('download-git-repo')
const path = require("path")
const ora = require('ora')

module.exports = function (target) {
 target = path.join(target || '.', '.download-temp');
 return new Promise(function (res, rej) {
  // 這里可以根據(jù)具體的模板地址設(shè)置下載的url,注意,如果是git,url后面的branch不能忽略
  let url='github:ZoeLeee/BaseLearnCli#bash';
  const spinner = ora(`正在下載項目模板,源地址:${url}`)
  spinner.start();

  download(url, target, { clone: true }, function (err)
  {
    if (err) {
      download(url, target, { clone: false }, function (err)
      {
        if (err) {
          spinner.fail();
          rej(err)
        }
        else {
          // 下載的模板存放在一個臨時路徑中,下載完成后,可以向下通知這個臨時路徑,以便后續(xù)處理
          spinner.succeed()
          res(target)
        }
      })
    }
    else {
      // 下載的模板存放在一個臨時路徑中,下載完成后,可以向下通知這個臨時路徑,以便后續(xù)處理
      spinner.succeed()
      res(target)
    }
  })
 })
}

這里注意下下載地址的url,注意url的格式,不是git clone 的那個地址。其中有個clone:false這個參數(shù),如果只是個人用,可以為true,這樣就相當(dāng)于執(zhí)行的git clone的操作,如果給其他人,可能會出錯,用false的話,那個就是直接用http協(xié)議去下載這個模板,具體可以去看官網(wǎng)的文檔.

inquirer.js 處理命令交互

比較簡單,可以看init.js

這里把獲取到的輸入信息在往下傳遞去處理。

metalsmith

接著,要根據(jù)獲取到的信息,渲染模板。

首先,未不影響原來的模板運行,我們在git倉庫上創(chuàng)建一個package_temp.json,對應(yīng)上我們要交互的變量名

{
 "name": "{{projectName}}",
 "version": "{{projectVersion}}",
 "description": "{{projectDescription}}",
 "main": "./src/index.js",
 "scripts": {
  "dev": "webpack-dev-server --config ./config/webpack.config.js",
  "build": "webpack --config ./config/webpack.config.js --mode production"
 },
 "author": "{{author}}",
 "license": "ISC",
 "devDependencies": {
  "@babel/core": "^7.3.3",
  "@babel/preset-env": "^7.3.1",
  "@babel/preset-react": "^7.0.0",
  "babel-loader": "^8.0.5",
  "clean-webpack-plugin": "^1.0.1",
  "css-loader": "^2.1.0",
  "html-webpack-plugin": "^3.2.0",
  "style-loader": "^0.23.1",
  "webpack": "^4.28.2",
  "webpack-cli": "^3.1.2",
  "webpack-dev-server": "^3.2.0"
 },
 "dependencies": {
  "react": "^16.8.2",
  "react-dom": "^16.8.2"
 }
}

在lib下創(chuàng)建generator.js文件,用來處理模板

const Metalsmith = require('metalsmith')
const Handlebars = require('handlebars')
const remove = require("../lib/remove")
const fs = require("fs")
const path = require("path")

module.exports = function (context) {
 let metadata = context.metadata;
 let src = context.downloadTemp;
 let dest = './' + context.root;
 if (!src) {
  return Promise.reject(new Error(`無效的source:${src}`))
 }

 return new Promise((resolve, reject) => {
  const metalsmith = Metalsmith(process.cwd())
   .metadata(metadata)
   .clean(false)
   .source(src)
   .destination(dest);
  // 判斷下載的項目模板中是否有templates.ignore
  const ignoreFile = path.resolve(process.cwd(), path.join(src, 'templates.ignore'));

  const packjsonTemp = path.resolve(process.cwd(), path.join(src, 'package_temp.json'));

  let package_temp_content;

  if (fs.existsSync(ignoreFile)) {
   // 定義一個用于移除模板中被忽略文件的metalsmith插件
   metalsmith.use((files, metalsmith, done) => {
    const meta = metalsmith.metadata()
    // 先對ignore文件進(jìn)行渲染,然后按行切割ignore文件的內(nèi)容,拿到被忽略清單
    const ignores = Handlebars
     .compile(fs.readFileSync(ignoreFile).toString())(meta)
     .split('\n').map(s => s.trim().replace(/\//g, "\\")).filter(item => item.length);
    //刪除被忽略的文件
    for (let ignorePattern of ignores) {
     if (files.hasOwnProperty(ignorePattern)) {
      delete files[ignorePattern];
     }
    }
    done()
   })
  }
  metalsmith.use((files, metalsmith, done) => {
   const meta = metalsmith.metadata();
   package_temp_content = Handlebars.compile(fs.readFileSync(packjsonTemp).toString())(meta);
   done();
  })

  metalsmith.use((files, metalsmith, done) => {
   const meta = metalsmith.metadata()
   Object.keys(files).forEach(fileName => {
    const t = files[fileName].contents.toString()
    if (fileName === "package.json")
     files[fileName].contents = new Buffer(package_temp_content);
    else
     files[fileName].contents = new Buffer(Handlebars.compile(t)(meta));
   })
   done()
  }).build(err => {
   remove(src);
   err ? reject(err) : resolve(context);
  })
 })
}

通過Handlebars給我們的package_temp.json進(jìn)行插值渲染,然后把渲染好的文件內(nèi)容替換掉原先的package.json的內(nèi)容

其中有時候我們也需要輸入選擇某些文件不下載,所以,我們在模板倉庫加入一個文件,取名templates.ignore,
然后,跟處理package_temp.json類似,優(yōu)先渲染這個文件內(nèi)容,找出需要忽略的文件刪掉。最后,刪除臨時文件夾,把文件移動到項目的文件。這樣項目就差不多了。
加入刪除文件夾得功能,在lib創(chuàng)建remove.js

const fs =require("fs");
const path=require("path");

function removeDir(dir) {
 let files = fs.readdirSync(dir)
 for(var i=0;i<files.length;i++){
  let newPath = path.join(dir,files[i]);
  let stat = fs.statSync(newPath)
  if(stat.isDirectory()){
   //如果是文件夾就遞歸下去
   removeDir(newPath);
  }else {
   //刪除文件
   fs.unlinkSync(newPath);
  }
 }
 fs.rmdirSync(dir)//如果文件夾是空的,就將自己刪除掉
}

module.exports=removeDir;

結(jié)尾

關(guān)于美化得就不說了,大概的腳手架就以上這些內(nèi)容,當(dāng)然這些功能太過于簡單,我們還需要根據(jù)自己的需要,添加功能,比如說,是否要啟用Typescript,要less還是sass等,大概原來差不多,根據(jù)輸入,選擇加載哪些文件,大家自由擴展,謝謝閱讀.

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論