脚手架开发
具备脚手架开发的能力
1、可以减少开发人员配置脚手架带来的时间损耗(特殊功能可以fork脚手架后再自行定制);
2、统一项目结构,方便管理,也降低项目交接时带来的需要熟悉项目的时间;
3、方便统一技术栈,可以预先引入固定的组件库以及相关库;
意义:
提高开发人员在多个项目之间的快速切换能力,提高项目可维护性,统一公司技术栈,避免因为环境不同导致奇怪的问题。
常用库
- chalk:(控制台字符样式)
- commander: (实现 NodeJS 命令行)
- download:(实现文件远程下载)
- fs-extra:(增强的基础文件操作库)
- handlebars:实现模板字符替换)
- inquirer:(实现命令行之间的交互)
- log-symbols:(为各种日志级别提供着色符号)
- ora:(优雅终端 Spinner 等待动画)
- update-notifier:(npm 在线检查更新)
集成构建工具 【webpack/gulp】
手写q-cli脚手架,集成webpack打包工具
特点:
- webpack5现在打包速度也提升了不少
- 基于 webpack 构建,并带有合理的默认配置;
- 统一处理好webpack配置优化、打包优化等,用户无需重新配置
- 开发者可以做到零配置,就可以快速开发项目
- 暴露tool.config.js ,让用户可以自定义配置
- 通过配置脚手架tool.config.js配置webpack
需要用到的webpack工具:【需要安装在生产环境,否则用户使用的话,是找不到这个包,因为在nom install是安装这个包的dependencies】
- webpack-chain
- webpack-bundle-analyzer
- webpack-dev-server
- webpack-merge
思路:
-
1、在根目录下生成q.config.js配置文件
-
2、tool.config.js的配置与webpack配置合并
-
3、可注册run命令,通过npx tool-cli run,命令运行,执行webpack配置
- run.js 【webpack沙箱环境启动】
import webpackConfig from './lib/webpack/webpack.config' //webpack配置type OptionsType = {dev:string,build:string}// 可接受options参数 【如dev、build等】const run = (options:OptionsType)=>{const nowPath = process.cwd() //获取当前工作目录webpackConfig(options) //运行webpack配置}export default runq.config.ts声明,参考vue-cli配置
import HtmlWebpackPlugin from "html-webpack-plugin";import { Configuration as WebpackConfig } from "webpack";export declare type Configuration = {/*** 生成HTML页面配置:https://webpack.js.org/plugins/html-webpack-plugin/*/pages?: HtmlWebpackPlugin.Options;/*** 开发环境配置: https://webpack.js.org/configuration/dev-server/*/devServer?: {};/*** 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中。*/configureWebpack?: (config: WebpackConfig) => WebpackConfig;};
集成eslint、perttier
eslint
- Linter
它只是解析和报告代码。特别是,Linter 对象不处理配置对象或文件
Linter 是一个构造函数,您可以通过传入要使用的选项来创建一个新实例
支持typescript:
当分析 TypeScript 文件时, 需要使用@typescript-eslint类型检查器
- @typescript-eslint
流行脚手架源码分析 【vue-cli,create-react-app】
@vue/cli功能主要是三部分组成的:
@vue/cli负责命令行参数收集@vue/cli-service这个包是启动vue的引擎及核心,承载webpack配置。@vue/plugin-xxxvue-cli的插件,一个插件对应一个功能,与上边用户自定义feature选项一一对应。
upgrade 检查更新
- update-notifier:npm 在线检查更新
update-notifier检测更新机制是通过package.json文件的name字段值和version字段值来进行校验:它通过name字段值从 npmjs 获取库的最新版本号,然后再跟本地库的version字段值进行比对,如果本地库的版本号低于 npmjs 上最新版本号,则会有相关的更新提示。
// 引用 update-notifier 库,用于检查更新
const updateNotifier = require('update-notifier')
// 引用 chalk 库,用于控制台字符样式
const chalk = require('chalk')
// 引入 package.json 文件,用于 update-notifier 库读取相关信息
const pkg = require('../package.json')
// updateNotifier 是 update-notifier 的方法,其他方法可到 npmjs 查看
const notifier = updateNotifier({
// 从 package.json 获取 name 和 version 进行查询
pkg,
// 设定检查更新周期,默认为 1000 * 60 * 60 * 24(1 天)
// 这里设定为 1000 毫秒(1秒)
updateCheckInterval: 1000,
})
function updateChk() {
// 当检测到版本时,notifier.update 会返回 Object
// 此时可以用 notifier.update.latest 获取最新版本号
if (notifier.update) {
console.log(`New version available: ${chalk.cyan(notifier.update.latest)}, it's recommended that you update before using.`)
notifier.notify()
} else {
console.log('No new version is available.')
}
}
// 将上面的 updateChk() 方法导出
module.exports = updateChk
utils工具方法
import path from 'path';
import fs from 'fs-extra';
// 获取工程目录
export const appDirectory = fs.realpathSync(process.cwd());
// 解析路径
export const resolveApp = function (relativePath: string) {
return path.resolve(appDirectory, relativePath);
};
//异步等待
function sleep(timeout) {
return new Promise((resolve => {
setTimeout(resolve, timeout);
}));
}
优雅生成配置文件
- 生成q.config.ts 配置文件,可合并webpack配置
- 模板文件生成package.json
- 生成特定依赖
- react模板,添加react、react-dom依赖
- vue模板,添加vue依赖
- 针对模板项目【vue、react】生成特定的配置文件q.config.js
- vue模板,需要使用vue-loader
- 集成eslint配置, 生成.eslint.json
- 集成perttier,生成perttier.json
- 生成tsconfig.json
Generator.ts
- 关键代码
async render(source, additionalData = {}, ejsOptions = {}) {
const baseDir = extractCallDir(); // 获取调用者的文件目录路径
source = path.resolve(baseDir, source);
const _files = await globby(["**/*"], {
cwd: source, // 当前工作目录
dot: true // 允许匹配以.开头的
});
const data = this._resolveData(additionalData);
for (const rawPath of _files) {
const sourcePath = path.resolve(source, rawPath);
// 解析文件内容
const content = await this.renderFile(sourcePath, data, ejsOptions);
Object.assign(this.files, { [rawPath]: content });
}
}
// 利用 ejs 渲染模板文件,解析文件内容
async renderFile(name, data, ejsOptions) {
const template = fs.readFileSync(name, "utf-8");
const yamlFront = await import("yaml-front-matter");
// 解析为 YAML 文档
const parsed = yamlFront.loadFront(template);
const content = parsed.__content;
let finalTemplate = content.trim() + `\n`;
// 返回渲染结果的字符串
return ejs.render(finalTemplate, data, ejsOptions);
}
模板渲染
- 将命令行的参数传入到模版里面,渲染出来,然后写入到文件中就可以了
ejs渲染引擎
- 用来将
EJS模版结合着JSON数据转换为HTML
基本语法:
- 模板语法:
- <%= %> : 输出到html页面
- <% %> : 一般用于控制语句和声明语句,比如:if…else,switch,for,var声明等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= projectname %></title>
</head>
<body>
<div>
<%= projectname %>
</div>
</body>
</html>
- render
- 将字符串作为模板,是一种同步方法
ejs.render(str,{
projectname:'xxx'
}, options)
- renderFile
filename参数是目标文件路径data是你要传进模板的数据对象option是配置选项, 可以省略, 具体内容查看文档function(err, str){}是回调函数.err是错误信息, 没有错误时为null.str是模板生成的 HTML 内容.
ejs.renderFile(filename, {
projectname:'xxx'
}, options, function(err, str){});
发布脚手架
1、npm公开包的名称需要是唯一的。
2、指定官方源,确保能发包成功
- package.json的
publishConfig字段指定官方源- registry 源地址
- tag 发布对应的
dist-tag标签 - access 如果是
scoped包,一定需要设置为public(付费账号除外)
因为淘宝源只能下载包,不能上传包。但是不用淘宝源,安装其他依赖又慢的不行。 解决方法: 安装依赖,统一从淘宝源拉,保证依赖安装速度。发包时借助 npm包的package.json的
publishConfig字段指定官方源,确保能发包成功
"publishConfig": {
"registry": "https://registry.npmjs.org/", //发布到npm官网上,不是淘宝源
"access": "public"
},
遇到问题总结
ora模块问题
- 安装版本v6+, import ora from 'ora'出现报错的情况的话, 可安装v5+版本
- v5版本 是用commonjs模块的
- v6版本 是ES模块
npm i -S ora@5
使用ESM模块问题
如何将我的 CommonJS 项目移动到 ESM / 如何让我的 TypeScript 项目输出 ESM?
- 参考:https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#readable-named-exports
- 参考:https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#readable-named-exports
优雅读取package.json
const pkg = fs.readFileSync('./package.json')
const json = JSON.parse(pkg)
webpack-dev-server问题
V3版本和V4版本区别:
const compiler = Webpack(devConfig);
const devServer = new WebpackDevServer(
{ hot: true, open: true, host: "localhost", port: 8000 },
compiler
);
devServer.startCallback(() => {
console.log("启动中...");
});