2023-03-09
Node.js
00

目录

按需加载打包配置
自动生成模板文件
能帮我解决什么问题
开撸
自动生成目录列表
能帮我解决什么问题
开撸
最后

image.png

上篇50+行代码搞定一行命令更新Npm包 - 掘金 (juejin.cn)介绍了自动化push仓库&&自动化更新版本等功能的实现。这篇说说自动生成模板文件、rollup按需加载打包配置以及自动生成目录列表,简单得不像话!

按需加载打包配置

按需加载听起来高大上,但本质就是将不同的模块功能文件分开打包成多个文件,而不是全部打包成一个主文件。

首先创建一个rollup.config.js 文件,然后下载需要的插件,创建文件pluginsCommon.js 用于配置共同的插件,相关代码如下:

JavaScript
const { 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 时,获取该文件的相对路径pushbuildInputs数组中;若文件类型是一个文件夹时,获取该文件夹下的文件目录,重复上一步操作。

实现上述需要使用nodeJs fs模块以及path模块,先进行导入:

JavaScript
const 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 }; } //...

获取文件相对路径并pushbuildInputs 数组:

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

自动生成模板文件

能帮我解决什么问题

  1. 每次想增加一个方法文件时,我需要创建四个文件,分别是src文件下的xxx.ts、测试test文件夹下的xxx.test.ts、文档docs文件夹下的xxx.md 以及example文件下的xxx.ts,我想要自动进行创建;
  2. 增加的方法,需要在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 ,开发者使用更方便。

TypeScript
import fs from "fs/promises"; import { srcPath } from "./const"; export default async () => { const dirLists = await fs.readdir(srcPath); return dirLists; }

srcPath 其实就是一个绝对路径,const.ts 文件下就一行代码搞定。

TypeScript
import 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 } //... }

命名校验:

  • 不能含有特殊字符;
  • 不能使用中文。

效果演示:

文件命名后,接下来就是创建对应的文件:

TypeScript
import 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的路径。

TypeScript
import 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 文件,用于抽离公共的创建文件夹与创建文件并写入的功能:

TypeScript
import 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文件粘贴出来:

TypeScript
import { 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方法:

TypeScript
import 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进行分割,把内容分成三部分存放至一个数组中。

TypeScript
import 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 导入文件,第二、三部分都是导出。导入内容部分用于判断是否为重复导入,导出部分以{ 再次分割,分割完成后再把新增方法名重新拼接。

TypeScript
const 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

TypeScript
const 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拼接。

TypeScript
const 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

TypeScript
const 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 许可协议。转载请注明出处!