工程化可以理解为使用一些方式,去改良然后提高行业中现有的步骤、设计、应用方式。
前端工程化,就是指对前端进行一些流程的标准化,让开发变得更有效率,且更好地做产品交付。
模块化
模块化指的是将代码功能做拆分,分成独立地能相互依赖的片段。
资源整合模块化
不同类型的资源无法组织在一起,比如 JavaScript 引擎能识别引入的 JS 文件,但无法识别 CSS 文件。如果我们希望所有的资源都能组织在一起进行管理,要比分别管理一个个不同类型的资源要方便地多。
为了解决这个问题,Webpack 诞生了。Webpack 是一个模块打包器,能够将任何资源转换为 JS 代码进行导入。 比如图片,它可以先变成一个静态资源服务的一个资源,然后在 JS 文件 import 的时候在转换为一个 URL 字符串,或者直接就变成一个 Base64 字符串。
这些需要使用到一些 Loader(加载器)。Webpack 是一个框架,使用者需要根据需求,添加一些 Loader,去识别不同的文件,转化成 JS 代码导入。
此外还有 Plugin(插件),在这整个流程中做一些处理,比如将导出的 JS 文件插入到 HTML 模板中,或是进行代码的压缩等等。
组件化
组件化是 UI 层面上的更细粒度的拆分,一种类似 div 等原生元素的 “自定义元素”。
组件有自己的 HTML、CSS 和 JS,同时有自己的状态,并支持嵌入到其他组件中并接受外部的数据,可以进行复用。组件化可以看作是 UI 层组织方式的一种模块化。
原本的以资源类型为单位进行组织的管理(所有 JS 文件放一个文件夹,CSS 同理),其实维护起来比较困难,也不好复用,组件化的构想是以视觉为单位进行拆分,做了结构、样式、脚本的组装,抽象出一个“新的元素”。常见的组件化框架有 Vue、React 等。
规范化
前端代码的规范是很重要的,能让代码能够写得更容易更正确,避免一些不必要的错误。
能想到的规范有:
- 目录结构规定。
- 代码风格(包括 JS、HTML、CSS)。
- 注释规范。
- commit message 规范。
- Git 工作流规范。
- Code Review。
- 请求接口规范。
有些规范不太能用工具进行限制,比如目录结构。但有些规范是可以利用到工具的。
自动化
自动化指的是用工具来做一些较为重复且简单的任务,让开发者从这些琐碎的事情中解放出来,提高效率。
比如前端开发中的自动化构建、自动化测试、自动化部署等等。 Gulp、Webpack、ESLint、Babel 等都能用于不同的自动化任务。
前端工程化是为了让前端开发更现代化、更高效率、更规范化、更可维护。
babel 在前端工程的应用
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
babel 目前在很多项目中不是必须使用的,并且在某些场景下根本不需要使用 babel 来进行编译,比如我们提到的,在一些库的封装时,我们可以选用 ESBuild 或 swc。
babel 有几个需要熟悉的包,分别是:
- @babel/cli,babel 脚手架工具
- @babel/core,babel 核心
- @babel/preset-env,一些预设
- @babel/generator,代码生成
- @babel/parser,转化
- @babel/types,主要用来创建、判断类型等 搭配@babel/generator来生成代码
- @babel/template,基于模板辅助创建 ast
babel 的流程
开发并调试 babel 插件
经常使用的链式调用语法
- 创建项目:首先,创建一个新的npm项目,并安装
@babel/core、@babel/parser、@babel/traverse、@babel/types依赖项。
$ mkdir custom-babel-plugin
$ cd custom-babel-plugin
$ npm init -y
$ npm install --save-dev @babel/core @babel/parser @babel/traverse @babel/types
- 创建插件文件:在项目目录下创建一个新的JavaScript文件,例如
custom-plugin.js。 - 编写插件代码:在
custom-plugin.js文件中编写自定义插件代码。Babel插件是一个函数,接受一个babel参数,可以使用这个参数访问Babel提供的API。
module.exports = function customPlugin(babel) {
const { types: t } = babel; // 获取 Babel types API
return {
name: "custom-plugin", // 插件名称
visitor: { // 访问者对象,用于访问抽象语法树节点
CallExpression(path, state) {
// 如果调用的函数名是 "customFunc",则在函数调用前添加一条日志
if (path.node.callee.name === "customFunc") {
const logStatement = t.StringLiteral("Calling customFunc...");
path.insertAfter(t.ExpressionStatement(logStatement));
}
}
}
};
};
以上插件代码定义了一个名为customPlugin的插件函数,它将在Babel编译中被调用。插件代码使用访问者模式遍历抽象语法树,查找符合条件的语法结构,并进行转换。示例代码展示了一个针对CallExpression节点的访问者对象,如果节点调用名是customFunc,则在节点后面插入一条日志语句。
- 配置Babel:在项目根目录创建
.babelrc文件,告诉Babel要使用自定义插件。
{
"plugins": ["./custom-plugin.js"]
}
在以上配置中,将自定义插件文件路径添加到了plugins数组中。
- 测试插件:编写一些测试代码用来测试自定义插件。
- 运行测试:使用
@babel/cli命令行工具来编译JavaScript代码,并输出到控制台。
$ npx babel test.js
调试插件:使用@babel/parser将JavaScript代码解析成抽象语法树,然后将语法树作为参数传递给插件以进行调试。
const parser = require("@babel/parser");
const traverse = require("@babel/traverse");
const generate = require("@babel/generator");
const code = "customFunc();";
const ast = parser.parse(code);
traverse.default(ast, customPlugin()); // 调用自定义插件进行转换
const output = generate.default(ast);
console.log(output.code); // 输出转换后的代码
以上调试代码将JavaScript代码解析成抽象语法树,然后将语法树作为参数传递给自定义插件,最终输出转换后的代码。
可以借鉴的 babel plugin
- antd 4.x - 按需github.com/umijs/babel…
- Babel 作者讲的,www.youtube.com/watch?v=UeV…
前端脚手架核心原理
- 开发新项目,很多逻辑比如:项目架构、接口请求、状态管理、国际化、换肤等之前项目就已经存在,这时,我们选择ctrl + c,ctrl + v 二连,新项目搭建完成,无非是要改改一些文件和包名;
- 项目增加某个模块时,复制一个已有模块,改改名字,新的模块就算创建成功了;
使用复制粘贴有以下缺点:
- 重复性工作,繁琐而且浪费时间
- copy 过来的模板容易存在无关的代码
- 项目中有很多需要配置的地方,容易忽略一些配置点
- 人工操作永远都有可能犯错,建新项目时,总要花时间去排错
- 框架也会不断迭代,人工建项目不知道最新版本号是多少,使用的依赖都是什么版本,很容易 bug 一大堆。
怎么去解决这些问题呢?
脚手架能够规避很多人为操作的问题,因为脚手架能够根据你事先约定的规范,创建项目,定义新的模块,打包,部署等等都能够在一个命令敲击后搞定,提升效率
编译原理:www.bilibili.com/video/BV1Ns…
脚手架所需依赖
| 库名 | 描述 |
|---|---|
| commander | 处理控制台命令 |
| chalk | 五彩斑斓的控制台 |
| semver | 版本检测提示 |
| fs-extra | 更友好的 fs 操作 |
| inquirer | 控制台询问 |
| execa | 执行终端命令 |
| download-git-repo | git 远程仓库拉取 |
脚手架的职责
脚手架可以为我们做很多事情,比如项目的创建、项目模块的新增、项目打包、项目统一测试、项目发布等,我先与大家聊聊最初始的功能,项目创建:
脚手架执行过程
上图向大家展示了创建项目和项目中创建模块的脚手架大致工作流程,下图更详细描述了基于模板创建的过程:
其实思路比较简单,当我们运行脚手架命令时,命令行会给出相关创建命令提示,我们只需要根据相关提示输入对应内容即可,项目相关信息键入完成后,脚手架会从远程仓库(这里的仓库可以是 github、gitlab 等)拉取模板资源,然后将模板中的内容替换,输出到新的文件夹中完成项目创建工作。
关于脚手架开发
我们需要了解:
怎么安装、怎么调试、实际开发如何开展
在每个前端项目中,都有package.json文件,它是项目的配置文件,常见的配置有配置项目启动、打包命令,声明依赖包等
当我们搭建一个新项目时,往往脚手架就帮我们初始化好了一个package.jaon配置文件,它位于项目的根目录中。
安装脚手架
如果脚手架在npm上, npm install yk-cli
如果在本地,就本地调试:
在脚手架项目根目录下,运行npm link
然后在需要使用的地方运行npm link yk-cli
开发脚手架的话,我们需要重点了解 package.json中的两个参数:
"main":"index.js" //表示文件的入口
"bin":{"yk":"bin/main.js"}
//bin 表示的是(一个可执行文件到指定文件源的映射),当我们这个库被当作依赖进行安装后,bin所指示的内容会注册一个软链接,并且是可执行
这样你才能在控制台输入:yk
package.json 与入口
项目结构如图
我们在上面有提到其实
"main":"index.js" 表示文件的入口,但是,我们从这个图中并没有发现index.js文件,所以在这个项目里面,"main":"index.js" 其实算是废的。此时它真正的主入口其实要看bin。
package.json 常见配置项如下:
在
package.json 中指明你的包通过怎样软链接的形式启动:bin 指定
因为是 package.json 包,我们需要注意 dependencies、devDependencies 和 peerDependencies 的区别。
dependencies字段中声明的是项目的生产环境中所必须的依赖包。
该字段的值是一个对象,该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围。devDependencies中声明的是开发阶段需要的依赖包,eg:Webpack、Eslint、Babel等,用于辅助开发。
它们不同于 dependencies,因为它们只需安装在开发设备上,而无需在生产环境中运行代码。当打包上线时并不需要这些包,所以可以把这些依赖添加到 devDependencies 中,这些依赖依然会在本地指定 npm install 时被安装和管理,但是不会被安装到生产环境中。peerDependencies字段是用来供插件指定其所需的主工具的版本。
"name": "chai-as-promised",
"peerDependencies": { "chai": "1.x" }
此代码指定在安装chai-as-promised模块时,主程序chai必须一起安装,而且chai的版本必须是1.x。如果项目指定的依赖是chai的2.0版本,就会报错。
定义好项目 package.json 文件后,安装相关依赖,就可以开始编写脚手架工程代码了。
我们首先编写脚手架入口文件,也就是 /bin/main.js , 该文件中定义的内容主要操作是通过 commander 处理控制台命令,解析参数,并根据不同参数处理不同的逻辑.
// 开始处理命令
const program = require('commander');
const minimist = require('minimist');
program.version(require('../package').version).usage('<command> [options]');
// 创建命令
program
.command('create <app-name>')
.description('create a new project')
.option(
'-p, --preset <presetName>',
'Skip prompts and use saved or remote preset',
)
.option('-d, --default', 'Skip prompts and use default preset')
.action((name, cmd) => {
const options = cleanArgs(cmd);
if (minimist(process.argv.slice(3))._.length > 1) {
console.log(
chalk.yellow(
'\n ⚠️ 检测到您输入了多个名称,将以第一个参数为项目名,舍弃后续参数哦',
),
);
}
require('../lib/create')(name, options);
});
我们bin目录里面一般都是放可执行的内容,而这些内容对应的逻辑是放在lib里的。 像上述例子中的create(创建命令),在lib中就对应lib/create.js
create 创建项目命令
通常我们习惯将包项目逻辑相关处理放在 lib 中,这样一来,对后面添加更多命令或操作更友好。
lib/create 文件主要处理文件名合法检测,文件是否存在等配置,检测无误,执行项目创建逻辑,该逻辑我们放在 lib/Creator 文件中处理。
// lib/create.js 创建项目
async function create(projectName, options) {
const cwd = options.cwd || process.cwd();
// 是否在当前目录
const inCurrent = projectName === '.';
const name = inCurrent ? path.relative('../', cwd) : projectName;
const targetDir = path.resolve(cwd, projectName || '.');
const result = validatePackageName(name);
// 如果所输入的不是合法npm包名,则退出
if (!result.validForNewPackages) {
console.error(chalk.red(不合法的项目名: "${name}"`));
result.errors &&
result.errors.forEach(err => {
console.error(chalk.red.dim('❌ ' + err));
});
result.warnings &&
result.warnings.forEach(warn => {
console.error(chalk.red.dim('⚠️ ' + warn));
});
exit(1);
}
// 检查文件夹是否存在
if (fs.existsSync(targetDir)) {
if (options.force) {
await fs.remove(targetDir);
} else {
await clearConsole();
if (inCurrent) {
const { ok } = await inquirer.prompt([
{
name: 'ok',
type: 'confirm',
message: Generate project in current directory?`,
},
]);
if (!ok) {
return;
}
} else {
const { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 目标文件夹 ${chalk.cyan(targetDir)} 已经存在,请选择:`,
choices: [
{ name: '覆盖', value: 'overwrite' },
{ name: '取消', value: false },
],
},
]);
if (!action) {
return;
} else if (action === 'overwrite') {
console.log(\nRemoving ${chalk.cyan(targetDir)}...`);
await fs.remove(targetDir);
}
}
}
}
await clearConsole();
// 前面完成准备工作,正式开始创建项目
const creator = new Creator(name, targetDir);
await creator.create(options);
}
module.exports = (...args) => {
return create(...args).catch(err => {
stopSpinner(false);
error(err);
});
};
通过以上操作,完成了创建项目前的准备工作,接下来正式进行创建,创建操作通过以下代码开始
const creator = new Creator(name, targetDir);
await creator.create(options);
创建逻辑我们放在另外文件中 /lib/Creator,该文件中我们主要进行的操作有:
- 拉取远程模板;
- 询问项目创建相关配置,比如:项目名、项目版本、操作人等;
- 将拉取的模板文件拷贝到创建项目文件夹中,生成 readme 文档;
- 安装项目所需依赖;
- 创建 git 仓库,完成项目创建。
const chalk = require('chalk');
const execa = require('execa');
const inquirer = require('inquirer');
const EventEmitter = require('events');
const loadRemotePreset = require('../lib/utils/loadRemotePreset');
const writeFileTree = require('../lib/utils/writeFileTree');
const copyFile = require('../lib/utils/copyFile');
const generateReadme = require('../lib/utils/generateReadme');
const { installDeps } = require('../lib/utils/installDeps');
const { defaults } = require('../lib/options');
const {
log,
error,
hasYarn,
hasGit,
hasProjectGit,
logWithSpinner,
clearConsole,
stopSpinner,
exit,
} = require('../lib/utils/common');
module.exports = class Creator extends EventEmitter {
constructor(name, context) {
super();
this.name = name;
this.context = context;
this.run = this.run.bind(this);
}
async create(cliOptions = {}, preset = null) {
const { run, name, context } = this;
if (cliOptions.preset) {
// awesome-test create foo --preset mobx
preset = await this.resolvePreset(cliOptions.preset, cliOptions.clone);
} else {
preset = await this.resolvePreset(
defaults.presets.default,
cliOptions.clone,
);
}
await clearConsole();
log(
chalk.blue.bold(
Awesome-test CLI v${require('../package.json').version}`,
),
);
logWithSpinner(✨`, 正在创建项目 ${chalk.yellow(context)}.`);
this.emit('creation', { event: 'creating' });
stopSpinner();
// 设置文件名,版本号等
const { pkgVers, pkgDes } = await inquirer.prompt([
{
name: 'pkgVers',
message: 请输入项目版本号`,
default: '1.0.0',
},
{
name: 'pkgDes',
message: 请输入项目简介`,
default: 'project created by awesome-test-cli',
},
]);
// 将下载的临时文件拷贝到项目中
const pkgJson = await copyFile(preset.tmpdir, preset.targetDir);
const pkg = Object.assign(pkgJson, {
version: pkgVers,
description: pkgDes,
});
// write package.json
log();
logWithSpinner('📄', 生成 ${chalk.yellow('package.json')} 等模板文件`);
await writeFileTree(context, {
'package.json': JSON.stringify(pkg, null, 2),
});
// 包管理
const packageManager =
(hasYarn() ? 'yarn' : null) || (hasPnpm3OrLater() ? 'pnpm' : 'npm');
await writeFileTree(context, {
'README.md': generateReadme(pkg, packageManager),
});
const shouldInitGit = this.shouldInitGit(cliOptions);
if (shouldInitGit) {
logWithSpinner(🗃`, 初始化Git仓库`);
this.emit('creation', { event: 'git-init' });
await run('git init');
}
// 安装依赖
stopSpinner();
log();
logWithSpinner(⚙`, 安装依赖`);
// log(⚙ 安装依赖中,请稍等...)
await installDeps(context, packageManager, cliOptions.registry);
// commit initial state
let gitCommitFailed = false;
if (shouldInitGit) {
await run('git add -A');
const msg = typeof cliOptions.git === 'string' ? cliOptions.git : 'init';
try {
await run('git', ['commit', '-m', msg]);
} catch (e) {
gitCommitFailed = true;
}
}
// log instructions
stopSpinner();
log();
log(🎉 项目创建成功 ${chalk.yellow(name)}.`);
if (!cliOptions.skipGetStarted) {
log(
👉 请按如下命令,开始愉快开发吧!\n\n` +
(this.context === process.cwd()
? ``
: chalk.cyan( ${chalk.gray('$')} cd ${name}\n`)) +
chalk.cyan(
` ${chalk.gray('$')} ${
packageManager === 'yarn'
? 'yarn start'
: packageManager === 'pnpm'
? 'pnpm run start'
: 'npm start'
}`,
),
);
}
log();
this.emit('creation', { event: 'done' });
if (gitCommitFailed) {
warn(
因您的git username或email配置不正确,无法为您初始化git commit,\n` +
请稍后自行git commit。\n`,
);
}
}
async resolvePreset(name, clone) {
let preset;
logWithSpinner(Fetching remote preset ${chalk.cyan(name)}...`);
this.emit('creation', { event: 'fetch-remote-preset' });
try {
preset = await loadRemotePreset(name, this.context, clone);
stopSpinner();
} catch (e) {
stopSpinner();
error(Failed fetching remote preset ${chalk.cyan(name)}:`);
throw e;
}
// 默认使用default参数
if (name === 'default' && !preset) {
preset = defaults.presets.default;
}
if (!preset) {
error(preset "${name}" not found.`);
exit(1);
}
return preset;
}
run(command, args) {
if (!args) {
[command, ...args] = command.split(/\s+/);
}
return execa(command, args, { cwd: this.context });
}
shouldInitGit(cliOptions) {
if (!hasGit()) {
return false;
}
// --git
if (cliOptions.forceGit) {
return true;
}
// --no-git
if (cliOptions.git === false || cliOptions.git === 'false') {
return false;
}
// default: true unless already in a git repo
return !hasProjectGit(this.context);
}
};
到这里,完成了项目的创建,接下来是项目的模块创建。
page 创建模块
我们回到入口文件bin/main.js,添加 page 命令的处理
// 创建页面命令
program
.command('page <page-name>')
.description('create a new page')
.option('-f, --force', 'Overwrite target directory if it exists')
.action((name, cmd) => {
const options = cleanArgs(cmd);
require('../lib/page')(name, options);
});
与 create 类似,我们page命令真正的逻辑处理放置在 lib/page 中, lib/page主要负责的内容和 create 类似,为创建模块做一些准备,比如检测项目中该模块是否已经存在,如果存在,询问是否覆盖等操作。
// lib/page.js
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const PageCreator = require('./PageCreator');
const validFileName = require('valid-filename');
const {
error,
stopSpinner,
exit,
clearConsole,
} = require('../lib/utils/common');
/**
* 创建项目
* @param {*} pageName
* @param {*} options
*/
async function create(pageName, options) {
// 检测文件名是否合规
const result = validFileName(pageName);
// 如果所输入的不是合法npm包名,则退出
if (!result) {
console.error(chalk.red(不合法的文件名: "${pageName}"`));
exit(1);
}
const cwd = options.cwd || process.cwd();
const pagePath = path.resolve(
cwd,
'./src/pages',
pageName.charAt(0).toUpperCase() + pageName.slice(1).toLowerCase(),
);
const pkgJsonFile = path.resolve(cwd, 'package.json');
// 如果不存在package.json,说明不再根目录,不能创建
if (!fs.existsSync(pkgJsonFile)) {
console.error(
chalk.red('\n' + '⚠️ 请确认您是否在项目根目录下运行此命令\n'),
);
return;
}
// 如果page已经存在,询问覆盖还是取消
if (fs.existsSync(pagePath)) {
if (options.force) {
await fs.remove(pagePath);
} else {
await clearConsole();
const { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 已存在 ${chalk.cyan(pageName)} 页面,请选择:`,
choices: [
{ name: '覆盖', value: true },
{ name: '取消', value: false },
],
},
]);
if (!action) {
return;
} else {
console.log(\nRemoving ${chalk.cyan(pagePath)}...`);
await fs.remove(pagePath);
}
}
}
// 前面完成准备工作,正式开始创建页面
const pageCreator = new PageCreator(pageName, pagePath);
await pageCreator.create(options);
}
module.exports = (...args) => {
return create(...args).catch(err => {
stopSpinner(false);
error(err);
});
};
检测完以后,通过上述代码中的如下部分,执行 page 创建的逻辑
// 前面完成准备工作,正式开始创建页面
const pageCreator = new PageCreator(pageName, pagePath);
await pageCreator.create(options);
在 lib/pageCreator 文件中,通过读取预先定义好的模板文件,生成目标文件.
在这里使用了一个模板语言——nunjucks,将生成页面的操作放置在 lib/utils/generatePage 文件中处理,如下:
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');
const nunjucks = require('nunjucks');
const { log, error, logWithSpinner, stopSpinner } = require('./common');
const tempPath = path.resolve(__dirname, '../../temp');
const pageTempPath = path.resolve(tempPath, 'page.js');
const lessTempPath = path.resolve(tempPath, 'page.less');
const ioTempPath = path.resolve(tempPath, 'io.js');
const storeTempPath = path.resolve(tempPath, 'store.js');
async function generatePage(context, { lowerName, upperName }) {
logWithSpinner(生成 ${chalk.yellow(${upperName}/${upperName}.js)}`);
const ioTemp = await fs.readFile(pageTempPath);
const ioContent = nunjucks.renderString(ioTemp.toString(), {
lowerName,
upperName,
});
await fs.writeFile(path.resolve(context, ./${upperName}.js`), ioContent, {
flag: 'a',
});
stopSpinner();
}
async function generateLess(context, { lowerName, upperName }) {
logWithSpinner(生成 ${chalk.yellow(${upperName}/${upperName}.less)}`);
const ioTemp = await fs.readFile(lessTempPath);
const ioContent = nunjucks.renderString(ioTemp.toString(), {
lowerName,
upperName,
});
await fs.writeFile(path.resolve(context, ./${upperName}.less`), ioContent, {
flag: 'a',
});
stopSpinner();
}
async function generateIo(context, { lowerName, upperName }) {
logWithSpinner(生成 ${chalk.yellow(${upperName}/io.js)}`);
const ioTemp = await fs.readFile(ioTempPath);
const ioContent = nunjucks.renderString(ioTemp.toString(), {
lowerName,
upperName,
});
await fs.writeFile(path.resolve(context, ./io.js`), ioContent, {
flag: 'a',
});
stopSpinner();
}
async function generateStore(context, { lowerName, upperName }) {
logWithSpinner(生成 ${chalk.yellow(${upperName}/store-${lowerName}.js)}`);
const ioTemp = await fs.readFile(storeTempPath);
const ioContent = nunjucks.renderString(ioTemp.toString(), {
lowerName,
upperName,
});
await fs.writeFile(
path.resolve(context, ./store-${lowerName}.js`),
ioContent,
{ flag: 'a' },
);
stopSpinner();
}
module.exports = (context, nameObj) => {
Promise.all([
generateIo(context, nameObj),
generatePage(context, nameObj),
generateStore(context, nameObj),
generateLess(context, nameObj),
]).catch(err => {
stopSpinner(false);
error(err);
});
};
在 PageCreator 中引入该文件,并执行,给一些提示,会更友好。
// lib/PageCreator.js
const chalk = require('chalk');
const EventEmitter = require('events');
const fs = require('fs-extra');
const generatePage = require('./utils/generatePage');
const {
log,
error,
logWithSpinner,
clearConsole,
stopSpinner,
exit,
} = require('../lib/utils/common');
module.exports = class PageCreator extends EventEmitter {
constructor(name, context) {
super();
this.name = name;
this.context = context;
}
async create(cliOptions = {}) {
const fileNameObj = this.getName();
const { context } = this;
await clearConsole();
log(
chalk.blue.bold(
Awesome-test CLI v${require('../package.json').version}`,
),
);
logWithSpinner(✨`, 正在创建页面...`);
// 创建文件夹
await fs.mkdir(context, { recursive: true });
this.emit('creation', { event: 'creating' });
stopSpinner();
console.log(context);
await generatePage(context, fileNameObj);
}
getName() {
const originName = this.name;
const tailName = originName.slice(1);
const upperName = originName.charAt(0).toUpperCase() + tailName;
const lowerName = originName.charAt(0).toLowerCase() + tailName;
return {
upperName,
lowerName,
};
}
};
到这里就完成了脚手架的项目创建和模块创建