上篇50+行代码搞定一行命令更新Npm包 - 掘金 (juejin.cn)介绍了自动化push仓库&&自动化更新版本等功能的实现。这篇说说自动生成模板文件、rollup按需加载打包配置以及自动生成目录列表,简单得不像话!
按需加载听起来高大上,但本质就是将不同的模块功能文件分开打包成多个文件,而不是全部打包成一个主文件。
首先创建一个rollup.config.js
文件,然后下载需要的插件,创建文件pluginsCommon.js
用于配置共同的插件,相关代码如下:
JavaScriptconst { getBabelOutputPlugin } = require('@rollup/plugin-babel')
//用于将typescript编译成javascript
const typescript = require('rollup-plugin-typescript2')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('@rollup/plugin-commonjs')
//用于压缩
const { terser } = require("rollup-plugin-terser")
module.exports = [
typescript(),
commonjs(),
resolve(),
getBabelOutputPlugin({
presets: ['@babel/preset-env'],
allowAllFormats: true,
}),
terser()
]
不了解的插件,请Google。
rollup打包运行环境是nodeJs,遵循commonJS的规范。
rollup提供了很多种打包格式rollup打包格式 ,我需要的是一个用于做CDN加速的medash.min.js
文件格式为umd
以及按需加载的若干cjs
格式文件。
不了解
umd、cjs
的读者,请查阅rollup打包格式 。
medash.min.js
文件容易得到,只需要设置打包入口文件,入口文件为main.ts
,向rollup.config.js
中添加如下内容即可:
JavaScript//...
const pluginsCommon = require('./pluginsCommon')
export default [
//...
{
input: "./main.ts",
output: [
{
file: 'dist/medash.min.js',
name: 'medash',
format: 'umd'
}
],
plugins: pluginsCommon
}];
按需加载打包配置稍微麻烦点,我需要获取所有需要按需打包的文件路径。一个文件一个文件的手动写入相当麻烦,为此我创建一个build.js
文件用于自动获取打包文件的路径。
我定义了一个变量名buildInputs
的数组,用于存储所有的文件路径,所有需要打包的文件均存于src
目录下。
思路: 获取src
目录下所有的文件或文件夹,逐一进行遍历。若文件类型为ts
时,获取该文件的相对路径push
到buildInputs
数组中;若文件类型是一个文件夹时,获取该文件夹下的文件目录,重复上一步操作。
实现上述需要使用nodeJs fs模块以及path模块,先进行导入:
JavaScriptconst fs = require('fs');
const Path = require('path');
fs.readdirSync(path)
获取文件夹目录文件:
JavaScript//...
function getFileNames(befter, after) {
let filesPath = Path.join(befter, after)
return { files: fs.readdirSync(filesPath), path: filesPath };
}
//...
获取文件相对路径并push
至buildInputs
数组:
JavaScript//...
function setBuildInputs(tsFile) {
let filePath = tsFile.split('src')[1]
buildInputs.push('./src' + filePath.replace(/\\/g, "/"))
}
//...
Path.extname(childrenPath)
获取文件后缀名,针对不同的后缀名做不同的逻辑处理:
JavaScript//...
function startBuild({ files, path }) {
if (files.length === 0) {
return;
}
files.forEach((file) => {
let childrenPath = Path.join(path, file);
let isExtname = Path.extname(childrenPath);
if (isExtname === '.ts') {
setBuildInputs(childrenPath);
} else if (isExtname === '.js') {
return;
} else {
startBuild(getFileNames(path, file));
}
})
}
startBuild(getFileNames(__dirname, '../src'));
//...
src
目录下残留一些.js
文件,选择性跳过。
效果演示:
源码地址:https://github.com/CatsAndMice/medash/blob/dev/rollup.config.js
src
文件下的xxx.ts
、测试test
文件夹下的xxx.test.ts
、文档docs
文件夹下的xxx.md
以及example
文件下的xxx.ts
,我想要自动进行创建;xxx.test.ts
、案例xxx.ts
导入以及main.ts
文件中导入导出步骤,我想要自动完成这一步的操作。50+行代码搞定一行命令更新Npm包 - 掘金 (juejin.cn)中,提及到了inquirer 。同样的,实现自动生成模板文件也从终端交互入手。
先在build
文件夹下,创建一个create.ts
文件。
我想增加一个方法时,终端应该提供一个目录选择,以便将增加的文件创建在对应的目录下,当我选择对应的目录后终端提供一个类似输入框的功能,用于给文件命名。
目录选择代码:
TypeScript/...
import getSrcLists from "./getSrcLists";
//...
async function typesCheck() {
const lists = await getSrcLists();
inquirer.prompt([
{
name: 'list',
type: 'list',
message: '请选择对应的文件夹',
choices: lists,
default: [lists[0]]
}
]).then(({ list }) => {
getName(list)
})
}
//...
其中getSrcLists
方法作用是获取src
下的文件夹名称,用于进行目录选择。import fs from "fs/promises";
导出的fs/promises
模块与import fs from "fs";
导出的fs
模块,区别在于fs
模块API采用的是回调,而fs/promises
模块的API支持Promise
,开发者使用更方便。
TypeScriptimport fs from "fs/promises";
import { srcPath } from "./const";
export default async () => {
const dirLists = await fs.readdir(srcPath);
return dirLists;
}
srcPath
其实就是一个绝对路径,const.ts
文件下就一行代码搞定。
TypeScriptimport path from 'path';
//...
export const srcPath = path.join(__dirname, '../src');
创建文件命名功能是getName(list)
方法,参数list
是用户选择的目录。
TypeScript//...
async function getName(fileName: string) {
//...
let { input } = await inquirer.prompt([
{
name: 'input',
type: 'input',
message: '请为创建的文件命名:',
}
])
if (isEmpty(input)) {
err('error:创建文件未命名');
return
}
const { isSpecialChar } = specialChar(input);
const { isCh } = ch(input);
if (isSpecialChar) {
err('error:文件名含有特殊字符!');
return
}
if (isCh) {
err('error:文件名含有中文!');
return
}
//...
}
命名校验:
效果演示:
文件命名后,接下来就是创建对应的文件:
TypeScriptimport path from "path";
import { testPath, srcPath, docsPath, examplePath, err } from './const';
//...
async function getName(fileName: string) {
const createPath = path.join(srcPath, fileName);
const createDocsPath = path.join(docsPath, fileName);
const createTestPathPath = path.join(testPath, fileName);
const createExamplePath = path.join(examplePath, fileName)
//...
createTestFile(createTestPathPath, input)
createFile(createPath, input);
createDocs(createDocsPath, input);
createExample(createExamplePath, input);
//...
}
//...
createPath、createTestPathPath、createDocsPath 、createExamplePath
这四个变量分别是src
文件下xxx.ts
、测试test
文件夹下xxx.test.ts
、文档docs
文件夹下xxx.md
以及example
文件夹下xxx.ts
的路径。
TypeScriptimport path from 'path';
//...
export const srcPath = path.join(__dirname, '../src');
export const testPath = path.join(__dirname, '../test');
export const docsPath = path.join(__dirname, '../docs/v3');
export const examplePath = path.join(__dirname, '../example');
//...
方法createTestFile、createFile、createDocs、createExample
逻辑类似,区别是创建的文件后缀名以及写入的内容。
创建createAndWrite.ts
文件,用于抽离公共的创建文件夹与创建文件并写入的功能:
TypeScriptimport fs from "fs";
import path from 'path';
import { err } from "./const";
export function create(createPath: string, suffixName: string) {
!fs.existsSync(createPath) && fs.mkdirSync(createPath);
return path.join(createPath, suffixName)
}
export function write(createPath: string, context: string, callBack = () => { }) {
if (fs.existsSync(createPath)) {
err(createPath + '文件已存在!')
return;
}
fs.writeFile(createPath, context, (error) => {
if (error) {
err(createPath + '文件创建失败!')
return;
}
callBack();
})
}
create
方法用于创建文件夹,write
方法用于创建文件并写入内容。
createTestFile、createFile、createDocs、createExample
逻辑是类似的,所以这里只把 createFile
文件粘贴出来:
TypeScriptimport { srcContext } from './const';
import { write, create } from './createAndWrite';
export default (createPath: string, name: string) => {
const suffixName = name + '.ts';
createPath = create(createPath, suffixName);
write(createPath, srcContext())
}
createTestFile、createFile、createDocs、createExample
文件中会改变的变量const suffixName = name + '.ts';
,还有就是srcContext
。
'.ts'
表示文件的后缀名,需要创建的文件后缀名有.md、.test.ts、.ts
; srcContext
是写入文件的内容函数,不同的文件写入的内容不同,而createTestFile、createFile、createDocs、createExample
中获取写入内容的函数名不同。
写入文件的内容是提前定义在consts.ts
文件中,这里代码就不粘贴了,感兴趣的读者请点击下面的源码地址。
源码地址:https://github.com/CatsAndMice/medash/blob/dev/build/const.ts
整体的代码目录:
源码地址:https://github.com/CatsAndMice/medash/tree/dev/build
最后,解决新增方法自动导入main.ts
还有导出问题 。
同样的,我在create.ts
文件中往getName
方法内添加一个addMainContext
方法:
TypeScriptimport path from "path";
//...
import addMainContext from './addMainContext';
//...
async function getName(fileName: string) {
const createPath = path.join(srcPath, fileName);
//...
createExample(createExamplePath, input);
addMainContext(createPath, input);
}
//...
addMainContext
文件中,代码逻辑为先读取main.ts
内容,然后进行内容分段,判断是否已重复来决定是否添加该新的方法,最后再重新写入main.ts
。
先看下main.ts
内容:
TypeScript//...
import toArray from "./src/Array/toArray";
import ch from "./src/RegExp/ch";
export {
ch,
toArray,
or,
chain,
composePromise
//...
}
export default {
ch,
toArray,
or,
chain,
composePromise
//...
}
它存在两种导出方式,导出方式均以export
关键字开头,所以读取main.ts内容后以export
进行分割,把内容分成三部分存放至一个数组中。
TypeScriptimport fs from "fs";
//...
export default (path: string, name: string) => {
filePath = path
fs.readFile('./main.ts', 'utf-8', (readError, data) => {
if (readError) {
console.error(readError);
return;
}
let context = addContext(data.split('export'), name);
context ? writeFile(context) : null
})
}
第一部分是import
导入文件,第二、三部分都是导出。导入内容部分用于判断是否为重复导入,导出部分以{
再次分割,分割完成后再把新增方法名重新拼接。
TypeScriptconst getFilePath = function (filePath: string, name: string) {
let afterPath = filePath.split('src')[1];
afterPath = afterPath.replace('\\', '/');
return './src' + afterPath + '/' + name;
}
const addContext = (args: string[], name: string) => {
let importsHeader = `import ${name} from "${getFilePath(filePath, name)}";\r\n`;
if (args[0].includes(importsHeader)) {
err(name + '已导入!');
return
}
let imports = args[0] + importsHeader;
let contexts = args[1].split('{');
let ctx = contexts[0] + `{\r\n${name},` + contexts[1];
let dafaultContexts = args[2].split('{')
let ctxs = dafaultContexts[0] + `{\r\n${name},` + dafaultContexts[1];
return [imports, ctx, ctxs].join('export');
}
最后,再将内容写入main.ts
。
TypeScriptconst writeFile = (context: string) => {
fs.writeFile('./main.ts', context, (error) => {
console.error(error);
})
}
export default (path: string, name: string) => {
filePath = path
fs.readFile('./main.ts', 'utf-8', (readError, data) => {
if (readError) {
console.error(readError);
return;
}
let context = addContext(data.split('export'), name);
context ? writeFile(context) : null
})
}
效果演示:
源码地址:https://github.com/CatsAndMice/medash/blob/dev/build/addMainContext.ts
这里我使用一个文档网站生成器docsify ,用于部署文档。
docs/v3
文件夹下,docsify生成文档网站,前提是要把文档文件路径以[xxx](相对路径)
的格式收录至_sidebar.md
文件中,一个一个的手动写入太麻烦,我想要自动进行录入。录入后,运行docsify可以得到一个在线文档网站链接了。
在自动生成模板文件一节中,有提及到一个createDocs
方法,它用于创建.md
文件并写入内容。自动生成目录实现从它这里入手。
先展示下它所处的目录:
从上图中注释,可以看出创建目录的入口为readDir
函数,该函数作为参数被传递到write
方法,这样做的目的是.md
创建后才执行readDir
更新目录。
获取文件夹目录,逐一遍历创建异步函数去拼接[xxx](相对路径)
,并将异步函数添加至promises
数组。
TypeScript//创建目录
const readDir = async () => {
let content = '* [快速开始](readme.md)\n';
const dirs = await fsPromises.readdir(docsPath);
const promises: any[] = [];
dirs.forEach((dir) => {
const promise = new Promise(async (resolve) => {
const filePath = path.join(docsPath, dir);
fsPromises.readdir(filePath).then(files => {
content += getContent(dir, files);
resolve(content);
})
})
promises.push(promise);
})
//闭包,方便获取content的值
allPromisesFinish(promises, () => content);
}
content += getContent(dir, files);
getContent
用于排序并拼接内容格式,拼接好的格式再与变量content
拼接。
TypeScriptconst getContent = (dir, files) => {
let text = `* ${dir}\n`;
files.sort((a, b) => a - b);
for (const file of files) {
text += ` * [${file.split('.')[0]}](v3/${dir}/${file})\n`;
}
return text;
}
promises
数组中所有的异步函数出结果后,将会执行() => { writeContent(content());}
写入内容至_sidebar.md
。
TypeScriptconst writeContent = (content) => {
const writeFilePath = path.join(__dirname, '../docs/_sidebar.md');
fsPromises.writeFile(writeFilePath, content);
}
//全部的Promise状态完成后才进行文件写入
const allPromisesFinish = (promises, content) => {
Promise.all(promises).then(() => {
writeContent(content());
})
}
一个简简单单的目录生成功能就完成了。
效果演示:
源码地址:https://github.com/CatsAndMice/medash/blob/dev/build/createDocs.ts
文章介绍了作者使用nodeJs解决一些重复性的操作,也算是对nodeJs文件模块的一种刻意练习。
码字不易!如果我的文章对你有帮助,你的👍就是对我的最大支持^_^。
本文作者:凌览
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!