這篇文章將為大家詳細講解有關web開發中如何搭建前端腳手架,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
腳手架的效果

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

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

主要3個,一個bin文件夾,放執行命令的入口文件
lib文件夾,放項目的主要文件,package.json不多說
這項目主要用到的幾個包
commander: 命令行工具
download-git-repo: 用來下載遠程模板
ora: 顯示loading動畫
chalk: 修改控制臺輸出內容樣式
log-symbols: 顯示出 √ 或 × 等的圖標
inquirer.js:命令交互
metalsmith:處理項目模板
handlebars:模板引擎
使用commander.js命令行工具
修改package.json的bin執行入口,
"bin": {
"lz": "./bin/www"
},"lz"這個命令可以自己選擇,然后在bin文件加創建名為www的文件,
#! /usr/bin/env node
require('../lib/index.js');其中
#! /usr/bin/env node
不能少,這個主要指定當前腳本由node.js進行解析
在lib創建一個index.js文件,
const program = require('commander')
program.version('1.0.0')
.usage('<command> [項目名稱]')
.command('init', '創建新項目')
.parse(process.argv);為方便測試,先鏈接到全局環境
npm link
執行下命令感受下
lz init hello
正常來說,應該就報錯了,錯誤堆棧大概就是確實www-init文件,
這是因為
commander支持git風格的子命令處理,可以根據子命令自動引導到以特定格式命名的命令執行文件,文件名的格式是[command]-[subcommand],例如:
macaw hello => macaw-hello
macaw init => macaw-init
所以我們 執行www文件的init,所以要在bin創建一個www-init文件,在lib創建個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>')
// 根據輸入,獲取項目名稱
let projectName = process.argv[2];
if (!projectName) { // project-name 必填
// 相當于執行命令的--help選項,顯示help信息,這是commander內置的一個命令選項
program.help()
return
}
const list = glob.sync('*') // 遍歷當前目錄
let next = undefined;
let rootName = path.basename(process.cwd());
if (list.length) { // 如果當前目錄不為空
if (list.some(n => {
const fileName = path.resolve(process.cwd(), n);
const isDir = fs.statSync(fileName).isDirectory();
return projectName === n && isDir
})) {
console.log(`項目${projectName}已經存在`);
return;
}
rootName = projectName;
next = Promise.resolve(projectName);
} else if (rootName === projectName) {
rootName = '.';
next = inquirer.prompt([
{
name: 'buildInCurrent',
message: '當前目錄為空,且目錄名稱和項目名稱相同,是否直接在當前目錄下創建新項目?',
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 => {
//刪除臨時文件夾,將文件移動到目標目錄下
return generator(context);
})
.then(context => {
// 成功用綠色顯示,給出積極的反饋
console.log(logSymbols.success, chalk.green('創建成功:)'))
console.log(chalk.green('cd ' + context.root + '\nnpm install\nnpm run dev'))
})
.catch(err => {
// 失敗了用紅色,增強提示
console.log(err);
console.error(logSymbols.error, chalk.red(`創建失?。?{err.message}`))
})
}init.js都做了什么呢?
首先,獲得 init 后面輸入的參數,作為項目名稱,當然判斷這個項目名稱是否存在,然后進行對應的邏輯操作,通過download-git-repo工具,下載倉庫的模板,然后通過inquirer.js 處理命令行交互,獲得輸入的名稱,版本號能信息,最后在根據這些信息,處理模板文件。
用download-git-repo下載模板文件
在lib下創建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) {
// 這里可以根據具體的模板地址設置下載的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 {
// 下載的模板存放在一個臨時路徑中,下載完成后,可以向下通知這個臨時路徑,以便后續處理
spinner.succeed()
res(target)
}
})
}
else {
// 下載的模板存放在一個臨時路徑中,下載完成后,可以向下通知這個臨時路徑,以便后續處理
spinner.succeed()
res(target)
}
})
})
}這里注意下下載地址的url,注意url的格式,不是git clone 的那個地址。其中有個clone:false這個參數,如果只是個人用,可以為true,這樣就相當于執行的git clone的操作,如果給其他人,可能會出錯,用false的話,那個就是直接用http協議去下載這個模板,具體可以去看官網的文檔.
inquirer.js 處理命令交互
比較簡單,可以看init.js

這里把獲取到的輸入信息在往下傳遞去處理。
metalsmith
接著,要根據獲取到的信息,渲染模板。
首先,未不影響原來的模板運行,我們在git倉庫上創建一個package_temp.json,對應上我們要交互的變量名
{
"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下創建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文件進行渲染,然后按行切割ignore文件的內容,拿到被忽略清單
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進行插值渲染,然后把渲染好的文件內容替換掉原先的package.json的內容
其中有時候我們也需要輸入選擇某些文件不下載,所以,我們在模板倉庫加入一個文件,取名templates.ignore,
然后,跟處理package_temp.json類似,優先渲染這個文件內容,找出需要忽略的文件刪掉。最后,刪除臨時文件夾,把文件移動到項目的文件。這樣項目就差不多了。
加入刪除文件夾得功能,在lib創建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;關于“web開發中如何搭建前端腳手架”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。