基於webpack4.x項目實戰1-簡單使用javascript
基於webpack4.x項目實戰2 - 配置一次,多個項目運行css
基於webpack4.x項目實戰3 - 手寫一個clivue
以前寫過webpack-multi的配置,css預處理器用的是less,今天咱們繼續這個系列的文章,來寫一個cli工具。因此本文其實不屬於webpack的範疇,而是教你寫一個屬於本身的cli工具,只是它是基於前面的兩篇文章來寫的,因此納入這個系列裏。java
咱們參考一下vue-cli,來學習一下怎麼寫cli工具。node
咱們先來看看vue-cli
的使用方法:webpack
npm install -g vue-cli
vue init webpack test
test爲你的項目名 而後會出現一些配置的問答Project name test: 項目名稱,若是不改,就直接原來的test名稱
Project description A Vue.js project: 項目描述,也可直接點擊回車,使用默認名字
Author: 做者
Vue build standalone:
Install vue-router? 是否安裝vue-router
Use ESLint to lint your code? 是否使用ESLint管理代碼
Pick an ESLint preset Standard: 選擇一個ESLint預設,編寫vue項目時的代碼風格
Set up unit tests Yes: 是否安裝單元測試
Pick a test runner jest
Setup e2e tests with Nightwatch? 是否安裝e2e測試
Should we run npm install for you after the project has been created? (recommended) npm:是否幫你`npm install`
複製代碼
參考vue-cli
,咱們的這個腳手架叫webpack-multi-cli
,是基於基於webpack4.x項目實戰2 - 配置一次,多個項目運行這個demo來構建的。git
完成後,咱們的命令也和vue-cli
相似github
npm install -g webpack-multi-cli
npm init project-name
,而後會出現如下配置選擇Project name: 項目名稱,若是不改,則爲npm init時的項目名
Project description A webpack-multi project: 項目描述,也可直接點擊回車,使用默認名字
Author: 做者
Pick a css preprocessor? 選擇一個css預處理器,可選擇是less或者是sass
Should we run npm install for you after the project has been created? (recommended) npm:是否幫你`npm install`,若是輸入npm命令,則幫你執行npm install
複製代碼
在開始構建以前,咱們須要安裝一些npm包:web
chalk
:彩色輸出
figlet
:生成字符圖案
inquirer
:建立交互式的命令行界面,就是這些問答的界面
inquirer
命令行用戶界面
commander
自定義命令
fs-extra
文件操做
ora
製做轉圈效果
promise-exec
執行子進程vue-router
看看咱們最終的package.json文件
{
"name": "webpack-multi-cli",
"version": "1.0.0",
"description": "create webpack-multi cli",
"main": "index.js",
"bin": {
"webpack-multi-cli": "bin/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/xianyulaodi/webpack-multi-cli.git"
},
"author": "xianyulaodi",
"license": "MIT",
"bugs": {
"url": "https://github.com/xianyulaodi/webpack-multi-cli/issues"
},
"dependencies": {
"chalk": "^2.4.2",
"commander": "^2.20.0",
"figlet": "^1.2.1",
"fs-extra": "^7.0.1",
"inquirer": "^5.2.0",
"ora": "^3.4.0",
"promise-exec": "^0.1.0"
},
"homepage": "https://github.com/xianyulaodi/webpack-multi-cli#readme"
}
複製代碼
咱們的入口文件爲bin/index.js
,相似於vue-cli init 項目名
,咱們一開始的命令爲:webpack-multi-cli init 項目名
,所以package.json的bin須要這樣寫
...
"bin": {
"webpack-multi-cli": "bin/index.js"
}
..
複製代碼
接下來編寫自定義的init命令,依賴於commander這個庫 lib/cmd.js
#!/usr/bin/env node
const program = require("commander");
const path = require('path');
const currentPath = process.cwd(); // 當前目錄路徑
const fs = require('fs-extra');
let projectName = null;
// 定義指令
program
.version(require('../package.json').version)
.command('init <name>')
.action(function (name) {
projectName = name;
});
program.parse(process.argv);
program.projectName = projectName;
if (!projectName) {
console.error('no project name was given, eg: webpack-multi-cli init myProject');
process.exit(1);
}
if (fs.pathExistsSync(path.resolve(currentPath, `${projectName}`))) {
console.error(`您建立的項目名:${projectName}已存在,建立失敗,請修改項目名後重試`);
process.exit(1);
}
module.exports = program;
複製代碼
若是沒有傳入項目名,或者傳入的項目名稱已經存在,則拋出異常,並結束程序,順便把項目名稱存起來,當作一個默認名稱。還記得咱們的命令不,
Project name test: // 項目名稱,若是不改,就直接原來的test名稱
複製代碼
這個默認的項目名稱就是這裏來的
這裏依賴於inquirer
這個庫
lib/inquirer.ja
const inquirer = require('inquirer');
const cmd = require('./cmd');
const projectName = cmd.projectName;
module.exports = {
getQuestions: () => {
const questions = [
{
name: 'projectName',
type: 'input',
message: 'Project name',
default: projectName
},
{
name: 'description',
type: 'input',
message: `Project description`,
default: 'A webpack-multi project'
},
{
name: 'author',
type: 'input',
message: `Author`,
},
{
name: 'cssPreprocessor',
type: 'list',
message: 'Pick an css preprocessor',
choices: [
"less",
"sass"
]
},
{
name: 'isNpmInstall',
type: 'input',
message: 'Should we run `npm install` for you after the project has been create?<recommended>',
}
];
return inquirer.prompt(questions);
},
}
複製代碼
主要就是咱們的一些交互問題,具體這個庫的用法,能夠google之
bin/index.js
咱們這邊的思路是,新建一個template目錄,用來存在咱們的webpack配置模板,將你輸入的那些問題,好比項目名,做者、描述、選擇less仍是sass等,寫入這些配置文件中,而後再下載到你執行命令的根目錄下
還有一種思路是獲取你回到的交互問題,而後從github中獲取文件,再下載到你執行命令的根目錄下
知道這個思路後,就比較簡單了
先來獲取你輸入命令的當前路徑
const currentPath = process.cwd(); // 當前目錄路徑
複製代碼
獲取輸入的交互問題
const config = await inquirer.getQuestions();
複製代碼
接下來,就將獲取的交互問題答案,寫入咱們的模板中便可
function handlePackageJson(config) {
const spinner = ora('正在寫入package.json...').start();
const promise = new Promise((resolve, reject) => {
const packageJsonPath = path.resolve(`${currentPath}/${config.projectName}`, 'package.json');
fs.readJson(packageJsonPath, (err, json) => {
if (err) {
console.error(err);
}
json.name = config.projectName;
json.description = config.description;
json.author = config.author;
if(config.cssPreprocessor == 'less') {
json.devDependencies = Object.assign(json.devDependencies, {
"less": "^3.9.0",
"less-loader": "^4.1.0"
});
} else {
json.devDependencies = Object.assign(json.devDependencies, {
"sass-loader": "^7.1.0",
"node-sass": "^4.11.0"
});
}
fs.writeJSON(path.resolve(`${currentPath}/${config.projectName}/package.json`), json, err => {
if (err) {
return console.error(err)
}
spinner.stop();
ora(chalk.green('package.json 寫入成功')).succeed();
resolve();
});
});
});
return promise;
}
複製代碼
webpack的交互問題比較簡單,就是選擇less仍是sass,默認爲less
function handleWebpackBase(config) {
const spinner = ora('正在寫入webpack...').start();
const promise = new Promise((resolve, reject) => {
const webpackBasePath = path.resolve(`${currentPath}/${config.projectName}`, '_webpack/webpack.base.conf.js');
fs.readFile(webpackBasePath, 'utf8', function(err, data) {
if (err) {
return console.error(err)
}
if(config.cssPreprocessor == 'scss') {
data = data.replace("less-loader", "sass-loader");
}
fs.writeFile(path.resolve(`${currentPath}/${config.projectName}/_webpack/webpack.base.conf.js`), data, (err,result) => {
if (err) {
return console.error(err)
}
spinner.stop();
ora(chalk.green('webpack 寫入成功')).succeed();
resolve();
})
})
});
return promise;
}
複製代碼
完整的主文件bin/index.js
#!/usr/bin/env node
const inquirer = require('../lib/inquirer');
const path = require('path');
const fs = require('fs-extra');
const ora = require('ora'); // 終端顯示的轉輪loading
const chalk = require('chalk');
const figlet = require('figlet');
const exec = require('promise-exec');
const currentPath = process.cwd(); // 當前目錄路徑
const templatePath = path.resolve(__dirname, '../template\/');
function handlePackageJson(config) {
const spinner = ora('正在寫入package.json...').start();
const promise = new Promise((resolve, reject) => {
const packageJsonPath = path.resolve(`${currentPath}/${config.projectName}`, 'package.json');
fs.readJson(packageJsonPath, (err, json) => {
if (err) {
console.error(err);
}
json.name = config.projectName;
json.description = config.description;
json.author = config.author;
if(config.cssPreprocessor == 'less') {
json.devDependencies = Object.assign(json.devDependencies, {
"less": "^3.9.0",
"less-loader": "^4.1.0"
});
} else {
json.devDependencies = Object.assign(json.devDependencies, {
"sass-loader": "^7.1.0",
"node-sass": "^4.11.0"
});
}
fs.writeJSON(path.resolve(`${currentPath}/${config.projectName}/package.json`), json, err => {
if (err) {
return console.error(err)
}
spinner.stop();
ora(chalk.green('package.json 寫入成功')).succeed();
resolve();
});
});
});
return promise;
}
function handleWebpackBase(config) {
const spinner = ora('正在寫入webpack...').start();
const promise = new Promise((resolve, reject) => {
const webpackBasePath = path.resolve(`${currentPath}/${config.projectName}`, '_webpack/webpack.base.conf.js');
fs.readFile(webpackBasePath, 'utf8', function(err, data) {
if (err) {
return console.error(err)
}
if(config.cssPreprocessor == 'scss') {
data = data.replace("less-loader", "sass-loader");
}
fs.writeFile(path.resolve(`${currentPath}/${config.projectName}/_webpack/webpack.base.conf.js`), data, (err,result) => {
if (err) {
return console.error(err)
}
spinner.stop();
ora(chalk.green('webpack 寫入成功')).succeed();
resolve();
})
})
});
return promise;
}
function successConsole(config) {
console.log('');
const projectName = config.projectName;
console.log(`${chalk.gray('項目路徑:')} ${path.resolve(`${currentPath}/${projectName}`)}`);
console.log(chalk.gray('接下來,執行:'));
console.log('');
console.log(' ' + chalk.green('cd ') + projectName);
if(config.isNpmInstall != 'npm') {
console.log(' ' + chalk.green('npm install'));
}
console.log(' ' + chalk.green('npm run dev --dirname=demo'));
console.log('');
console.log(chalk.green('enjoy coding ...'));
console.log(
chalk.green(figlet.textSync("webpack multi cli"))
);
}
function createTemplate(config) {
const projectName = config.projectName;
const spinner = ora('正在生成...').start();
fs.copy(path.resolve(templatePath), path.resolve(`${currentPath}/${projectName}`))
.then(() => {
spinner.stop();
ora(chalk.green('目錄生成成功!')).succeed();
return handlePackageJson(config);
})
.then(() => {
return handleWebpackBase(config);
})
.then(() => {
if(config.isNpmInstall == 'npm') {
const spinnerInstall = ora('安裝依賴中...').start();
if(config.cssPreprocessor == 'sass') {
console.log('若是node-sass安裝失敗,請查看:https://github.com/sass/node-sass');
}
exec('npm install', {
cwd: `${currentPath}/${projectName}`
}).then(function(){
console.log('')
spinnerInstall.stop();
ora(chalk.green('相賴安裝成功!')).succeed();
successConsole(config);
}).catch(function(err) {
console.error(err);
});
} else {
successConsole(config);
}
})
.catch(err => console.error(err))
}
const launch = async () => {
const config = await inquirer.getQuestions();
createTemplate(config);
}
launch();
複製代碼
最後,將咱們的腳手架發佈到npm便可,關於npm包的發佈便可,很早以前寫過一篇文件,能夠點擊查看如何寫一個npm包
咱們的包已經發布成功 www.npmjs.com/package/web…
接下來全局安裝一下 npm install webpack-multi-cli -g
使用webpack init myTest
,以下圖所示:
安裝依賴後,執行 npm run dev --dirname=demo
,就能夠看到咱們的效果了
至此,咱們的webpack-multi-cli腳手架就完成了,比較簡單,固然跟vue-cli是無法比的,不過基本思路就在這裏,若是想搞複雜一點的腳手架,其實也就是一個擴展而已。
經過這篇文章,但願你能夠學習到如何寫一個屬於本身的腳手架,瞭解到相似於vue-cli的基本思路,但願你能有些許的收穫