内容简介:本来想搭建一个webpack脚手架的,于是在搭建的过程中不断地搜集相关资料。可最终的结果是,webpack脚手架没有搭建成,却写出个 CLI 小工具。其实,这也并不是没有原因的。现在流行的框架都推出了自己的脚手架工具,比如,Vue CLI,Create React App 等。脚手架和CLI往往如影随形,这也导致了两者在概念上的混淆。标题为什么这么拗口,其实是为了区分这两个概念。既然被带跑偏了,就只能在跑偏的路上越跑越远吧。命令行界面(英语:Command-Line Interface,缩写:CLI)是在图
本来想搭建一个webpack脚手架的,于是在搭建的过程中不断地搜集相关资料。可最终的结果是,webpack脚手架没有搭建成,却写出个 CLI 小工具。其实,这也并不是没有原因的。现在流行的框架都推出了自己的脚手架工具,比如,Vue CLI,Create React App 等。脚手架和CLI往往如影随形,这也导致了两者在概念上的混淆。标题为什么这么拗口,其实是为了区分这两个概念。
我有一个想法
既然被带跑偏了,就只能在跑偏的路上越跑越远吧。
命令行界面(英语:Command-Line Interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(character user interface, CUI) ———— 维基百科
使用过 Vue CLI 的同学应该都知道,我们只需要在终端敲几个的命令就可以搭建一个 Vue 的脚手架。如果不使用 CLI 的话,每次创建项目时,都需要配置文件(比如webpack配置文件)、设计结构、技术栈选型等。如果每次从零开始去搭建项目就会很麻烦,所以我们可以把相同的东西抽离成脚手架。以后创建项目时,就可以直接把脚手架复制过来,并以此为基础搭建项目。
回过头来再看看我们手动搭建项目的过程,从每次从零开始搭建项目到脚手架的复用,这中间有了很大的进步。可即使是复制黏贴,我们依然觉得很麻烦,如果用命令行的方式来取代图形操作,我们就可以更懒一些了。
回到主题,我本来打算写的webpack脚手架是基于这样的一个想法。:arrow_right: 现在大部分的前端工程,webpack作为打包 工具 已经成了标配了。而 webpack 的配置是大同小异的,完全可以剥离出一个通用的webpack配置,然后针对个别配置进行修改。本次希望最终实现一个基于webpack适用于不同前端模板(React、Vue、ES+)的脚手架。
现在脚手架有了,如何 自动化 去搭建一个项目呢?
-
复制或下载脚手架模板。(为了更灵活,上传到GitHub,或发布npm中)。
-
根据不同需求,在脚手架模板基础上重新配置webpack、package文件。
-
安装依赖。
以下代码可见 GitHub 。
CLI 中的预备工作
首先了解一下 #!
。文件开头要加上 #! /usr/bin/env node
。
在计算领域中,Shebang(也称为 Hashbang )是一个由井号和叹号构成的字符序列 #! ,其出现在文本文件的第一行的前两个字符。 在文件中存在 Shebang 的情况下,类 Unix 操作系统的程序加载器会分析 Shebang 后的内容,将这些内容作为解释器指令,并调用该指令,并将载有 Shebang 的文件路径作为该解释器的参数。 ———— 维基百科
使用 #!/usr/bin/env 脚本解释器名称 是一种常见的在不同平台上都能正确找到解释器的办法。 ———— 维基百科
然后看看都用到了哪些东西(部分)。
npm install commander chalk fs-extra shelljs inquirer ora ejs --save 复制代码
#! /usr/bin/env node // multi-spa.js const program = require('commander'); // 解析命令; const chalk = require('chalk'); // 命令行界面输出美颜 const fs = require('fs-extra'); // fs的拓展; const shell = require('shelljs'); // 重新包装了 child_process; const inquirer = require('inquirer'); // 交互式问答; const ora = require('ora'); // 输出样式美化; const ejs = require('ejs'); // 模版引擎; const path = require('path'); const currentPath = process.cwd(); let answersConfig = null; 复制代码
命令的解析
类似与 Vue 的 vue init
,我们也希望自己的 CLI 也能拥有类似的功能。
// package.json "bin": { "multi-spa-webpack": "./bin/multi-spa.js" }, 复制代码
这样,我们就有了 multi-spa-webpack
的命令。如果我们想要全局使用,还需要执行下面命令。
npm link 复制代码
接下来就要初始化 multi-spa-webpack
相关的命令了。
// multi-spa.js program .command('init <项目路径> [选项]') .description('指令说明:初始化项目') .action(async (appName) => { try { let targetDir = path.resolve(currentPath, appName || '.'); if (fs.pathExistsSync(targetDir)) { if (program.force) { GenarateProject(appName); // 创建项目; } ora(chalk.red(`!当前目录下,${appName}已存在,请修改名称后重试`)).fail(); process.exit(1); }; answersConfig = await getAnswers(appName); GenarateProject(appName); // 创建项目; } catch (error) { ora(chalk.red(`项目创建失败:${error}`)).fail(); process.exit(1); } }); program .arguments('<command>') .action((cmd) => { console.log(); console.log(chalk.red(`!命令未能解析 <${chalk.green(cmd)}>`)); console.log(); program.outputHelp(); console.log(); }); program.parse(process.argv); if (program.args.length === 0) { console.log(); console.log(chalk.red('!输入的命令有误')); console.log(); chalk.cyan(program.help()); } 复制代码
复制或下载模板
在执行 multi-spa-webpack init spa-project
后,就需要拷贝一份脚手架到本地了。至于脚手架从哪里来,可以放在 github 上(类似 Vue CLI)或 放在 CLI 对应的目录下(类似create-react-app)。
本文是采用的是从 github 获取脚手架模板的。但是常规的方式,只能下载整个项目,而对于不需要的文件夹或文件,也会同时下载,下载后,只能在本地中删除无关文件了。我这里是从源头上剔除无关文件的下载,这个方法可能会有一些局限性吧(sparse-checkout)。不过两者最终的目的是一样的。
// multi-spa.js function DownTemplate(projectDir) { const remote = 'https://github.com/yexiaochen/multi-spa-webpack-cli.git'; const { template } = answersConfig; let downTemplateSpinner = ora(chalk.cyan('模板下载中...')).start(); return new Promise((resolve, reject) => { shell.exec(` mkdir ${projectDir} cd ${projectDir} git init git remote add -f origin ${remote} git config core.sparsecheckout true echo "template/common" >> .git/info/sparse-checkout echo "template/config" >> .git/info/sparse-checkout echo "template/services" >> .git/info/sparse-checkout echo "template/${template}" >> .git/info/sparse-checkout echo ".gitignore" >> .git/info/sparse-checkout echo "package.json" >> .git/info/sparse-checkout git pull origin master rm -rf .git mv template/* ./ rm -rf template `, (error) => { if (error) { downTemplateSpinner.stop() ora(chalk.red(`模板下载失败:${error}`)).fail() reject() } downTemplateSpinner.stop(); ora(chalk.cyan('模板下载成功')).succeed(); resolve(); }) }) } 复制代码
重新生成配置文件
像 webpack、package 等配置文件,也都是包含在脚手架里的,不过这些配置还不能直接拿来用。我们还需要通过交互式问答,来针对性得在现有的基础上重新生成配置文件。
// multi-spa.js function getAnswers(appName) { const options = [ { type: 'input', name: 'name', message: '项目名称', default: appName, }, { type: 'input', name: 'description', message: '项目描述', default: '单页面应用', }, { type: 'confirm', name: 'eslint', message: '是否启用 eslint+pretty', default: true }, { name: 'cssPreprocessor', type: 'list', message: 'CSS 预处理器', choices: [ "less", "sass", "none", ] }, { name: 'template', type: 'list', message: '选取模板', choices: [ "react", "vue", "es" ] }, ]; return inquirer.prompt(options); } 复制代码
在获得特定的需求后,还要把这些数据注入到配置文件中。就是通过模板引擎把数据塞到模板里。这里使用的是 ejs 模版引擎。
<!--webpack.common.ejs--> <%= answers.cssPreprocessor == 'none' ? /\.css$/ : (answers.cssPreprocessor == 'less' ? /\.less$/ : /\.scss$/) %> <%= answers.cssPreprocessor == 'none' ? '' : (answers.cssPreprocessor == 'less' ? 'less-loader' : 'sass-loader') %> 复制代码
// multi-spa.js async function GenarateWebpackConfig(targetDir) { try { const webpackConfigPath = path.resolve(`${currentPath}/${targetDir}/config`, 'webpack.common.ejs'); const webpackConfigTargetPath = path.resolve(`${currentPath}/${targetDir}/config`, 'webpack.common.js'); const webpackConfigSpinner = ora(chalk.cyan(`配置 webpack 文件...`)).start(); let webpackConfig = await fs.readFile(webpackConfigPath, 'utf8'); let generatedWebpackConfig = ejs.render(webpackConfig, { answers: answersConfig }); await Promise.all([ fs.writeFile(webpackConfigTargetPath, generatedWebpackConfig), fs.remove(webpackConfigPath) ]) webpackConfigSpinner.stop(); ora(chalk.cyan(`配置 webpack 完成`)).succeed(); } catch (error) { ora(chalk.red(`配置文件失败:${error}`)).fail(); process.exit(1); } } async function GenaratePackageJson(projectDir) { try { const { name, description, cssPreprocessor } = answersConfig; const packageJsonPath = path.resolve(`${currentPath}/${projectDir}`, 'package.json'); const packageJsonSpinner = ora(chalk.cyan('配置 package.json 文件...')).start(); let package = await fs.readJson(packageJsonPath); package.name = name; package.description = description; if (cssPreprocessor == 'less') { package.devDependencies = { ...package.devDependencies, "less-loader": "^5.0.0" } } if (cssPreprocessor == 'sass') { package.devDependencies = { ...package.devDependencies, "node-sass": "^4.12.0", "sass-loader": "^7.1.0" } } await fs.writeJson(packageJsonPath, package, { spaces: '\t' }); packageJsonSpinner.stop(); ora(chalk.cyan('package.json 配置完成')).succeed(); } catch (error) { if (error) { ora(chalk.red(`配置文件失败:${error}`)).fail(); process.exit(1); }; } } 复制代码
安装依赖
其实配置文件生成后,CLI 就快接近尾声了。剩下就是安装依赖。
// multi-spa.js function InstallDependencies(targetDir) { const installDependenciesSpinner = ora(chalk.cyan(`安装依赖中...`)).start(); return new Promise((resolve, reject) => { shell.exec(` cd ${targetDir} npm i `, (error) => { if (error) { installDependenciesSpinner.stop() ora(chalk.red(`依赖安装失败:${error}`)).fail() reject() } installDependenciesSpinner.stop(); ora(chalk.cyan('依赖安装完成')).succeed(); resolve(); }) }) } 复制代码
小结
一个粗糙的CLI,就这么完成了。把以上几个方法包装一下,就是本次 CLI 的全部内容了。
- 拷贝脚手架。2. 重新生成配置文件。3安装依赖。
async function GenarateProject(targetDir) { await DownTemplate(targetDir); await Promise.all([GenaratePackageJson(targetDir).then(() => { return InstallDependencies(targetDir); }), GenarateWebpackConfig(targetDir) ]); ora(chalk.cyan('项目创建成功!')).succeed(); } 复制代码
如果想要发布,需要登陆npm , npm publish
。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 9102年:从0开始手写一个Vue.js优化版脚手架
- 从零开始手写一个优化版的React脚手架Webpack最新版配置
- Next.js 脚手架进阶 —— 扩展为全栈脚手架
- 前后端分离脚手架
- 脚手架的开发总结
- Angular脚手架开发
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Modeling the Internet and the Web
Pierre Baldi、Paolo Frasconi、Padhraic Smyth / Wiley / 2003-7-7 / USD 115.00
Modeling the Internet and the Web covers the most important aspects of modeling the Web using a modern mathematical and probabilistic treatment. It focuses on the information and application layers, a......一起来看看 《Modeling the Internet and the Web》 这本书的介绍吧!