Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const fs = require('fs').promises
- const { createWriteStream, createReadStream } = require('fs')
- const { Duplex, Readable, Writable, Transform } = require('stream')
- const path = require('path')
- const pipeline = require('util').promisify(require('stream').pipeline)
- /**
- * 流默认配置
- */
- const STREAMCONFIG = { objectMode: true }
- const income = Symbol()
- const transfer = Symbol()
- /**
- * 工作流基类
- * 所有处理文件的方法都是此类的实例或者子类
- * 实例需要实现transform方法
- */
- class WorkThread extends Transform {
- constructor(opts) {
- super({ ...opts, ...STREAMCONFIG })
- }
- }
- /**
- * 文件处理基类
- * 通过一个函数返回处理后的内容,Buffer格式
- */
- class FileTransfer extends Duplex {
- /**
- * 将单个文件所有内容拼接后交给回调函数处理
- * @param {function} cb 处理文件内容的回调函数
- */
- constructor(cb) {
- super()
- this[income] = Buffer.from('')
- this.transfer = cb
- }
- _write(buf, enc, callback) {
- this[income] = Buffer.concat([this[income], buf])
- callback()
- }
- _read() {
- this.push(this[income] ? this.transfer(this[income]) : null)
- this[income] = null
- }
- }
- /**
- * 文件扫描
- * 提供文件信息的Map
- * filename 文件名,包含扩展名
- * path 文件相对路径
- * file 文件的读取流
- * name 文件名
- */
- class FileScanner extends Readable {
- /**
- * 扫描所有文件
- * @param {string} src 路径
- */
- constructor(src) {
- super(STREAMCONFIG)
- if (typeof src !== 'string') {
- throw new Error('Invalid src')
- }
- this.src = src
- this.list = []
- this.index = 0
- }
- async _read() {
- /**
- * 第一次执行
- * 如果没有文件列表就先读取文件列表
- */
- if (!this.list.length) {
- const arr = await deepReadDir(this.src)
- if (!arr.size) {
- return this.push(null)
- }
- this.list = [...arr]
- }
- if (this.index >= this.list.length) {
- return this.push(null)
- }
- const info = new Map
- const filepath = this.list[this.index++]
- const { base, dir, name } = path.parse(filepath)
- info.set('filename', base)
- info.set('path', path.relative(this.src, dir))
- info.set('name', name)
- info.set('file', createReadStream(filepath))
- this.push(info)
- }
- }
- /**
- * 文件输出
- * 需要提供文件信息的Map
- * filename 文件名,包含扩展名
- * path 相对路径
- * file 文件读取流
- */
- class FileOuputer extends Writable {
- constructor(dest) {
- super(STREAMCONFIG)
- if (typeof dest !== 'string') {
- throw new Error('Invalid dest')
- }
- this.dest = dest
- }
- async _write(info, enc, callback) {
- /**
- * 创建输出文件的目录
- */
- try {
- await fs.mkdir(path.join(this.dest, info.get('path')), { recursive: true })
- } catch (e) {
- console.error(e)
- return callback(e)
- }
- const output = createWriteStream(path.join(this.dest, info.get('path'), info.get('filename')))
- const dir = path.parse(output.path).dir
- console.log(path.relative(this.dest, output.path))
- /**
- * 输出到目标目录
- */
- try {
- await pipeline(info.get('file'), output)
- } catch (e) {
- console.error(e)
- } finally {
- callback()
- }
- }
- }
- /**
- * 处理mina文件,输出小程序4个文件
- */
- const minaWorker = new WorkThread({
- transform(chunk, enc, callback) {
- /**
- * 此方法只对vue和mina格式文件生效
- * 其余文件直接跳过
- */
- const filename = chunk.get('filename')
- if (!/\.(vue|mina)$/i.test(chunk.get('filename'))) {
- return callback(null, chunk)
- }
- /**
- * 生成样式
- */
- const styleInfo = new Map(chunk.entries())
- .set('filetype', 'style')
- .set('filename', chunk.get('name') + '.wxss')
- const styleStream = new FileTransfer(buf => {
- const str = buf.toString()
- const styleMatches = str.match(/<style.*?>([\s\S]*?)<\/style>/sm)
- if (!styleMatches) {
- return Buffer.from('')
- }
- return Buffer.from(styleMatches[1].trim(), 'utf8')
- })
- styleInfo.set('file', chunk.get('file').pipe(styleStream))
- this.push(styleInfo)
- /**
- * 生成页面模板
- */
- const wxmlInfo = new Map(chunk.entries())
- .set('filetype', 'wxml')
- .set('filename', chunk.get('name') + '.wxml')
- const wxmlStream = new FileTransfer(buf => {
- const str = buf.toString()
- const matches = str.match(/<template.*?>([\s\S]*?)<\/template>/sm)
- if (!matches) {
- return Buffer.from('')
- }
- return Buffer.from(matches[1].trim(), 'utf8')
- })
- const wxmlReader = createReadStream(chunk.get('file').path)
- wxmlInfo.set('file', wxmlReader.pipe(wxmlStream))
- this.push(wxmlInfo)
- /**
- * 生成js
- */
- const jsInfo = new Map(chunk.entries())
- .set('filetype', 'js')
- .set('filename', chunk.get('name') + '.js')
- const jsStream = new FileTransfer(buf => {
- const str = buf.toString()
- const matches = str.match(/<script.*?>([\s\S]*?)<\/script>/sm)
- if (!matches) {
- return Buffer.from('')
- }
- return Buffer.from(matches[1].trim(), 'utf8')
- })
- const jsReader = createReadStream(chunk.get('file').path)
- jsInfo.set('file', jsReader.pipe(jsStream))
- this.push(jsInfo)
- /**
- * 生成json
- */
- const configInfo = new Map(chunk.entries())
- .set('filetype', 'json')
- .set('filename', chunk.get('name') + '.json')
- const jsonStream = new FileTransfer(buf => {
- const str = buf.toString()
- const matches = str.match(/<script.*?>([\s\S]*?)<\/script>/sm)
- if (!matches) {
- return Buffer.from('{}')
- }
- const jsonData = new Function('App', 'Page', 'Component', matches[1].replace(/((Page|App|Componet)\()/, 'return $1'))
- function _g(obj) {
- return obj
- }
- const config = jsonData(_g, _g, _g).config
- if (!config) {
- return Buffer.from('{}', 'utf8')
- }
- return Buffer.from(JSON.stringify(config), 'utf8')
- })
- const jsonReader = createReadStream(chunk.get('file').path)
- configInfo.set('file', jsReader.pipe(jsonStream))
- this.push(configInfo)
- return callback()
- }
- })
- /**
- * 处理
- */
- const copyWorker=new WorkThread({
- transform(info, enc, callback) {
- }
- })
- async function winnie(options) {
- console.time('used')
- const lines = new Set()
- .add(new FileScanner(options.get('src')))
- .add(minaWorker)
- .add(new FileOuputer(options.get('dest')))
- try {
- await pipeline(...lines)
- } catch (e) {
- console.error(e)
- } finally {
- console.timeEnd('used')
- }
- }
- function start() {
- const args = parseParams()
- winnie(args).catch(e => console.error(e.message))
- }
- /**
- * 获取命令行参数
- * @returns {Map}
- */
- function parseParams() {
- let result = new Map
- let args = process.argv.slice(2)
- args.forEach((key, index, arr) => {
- if (/^\-{2}\S+/.test(key)) {
- // --name
- let name = key.match(/^\-+(\S+)$/)[1]
- if (typeof arr[index + 1] === 'string' && !/^\-{2}\S+/.test(arr[index + 1])) {
- result.set(name, arr[index + 1])
- // result[name] = arr[index + 1]
- } else {
- result.set(name, true)
- // result[name] = true
- }
- }
- })
- return result
- }
- /**
- * 循环获取目录文件
- * @param {string} src 目录
- * @returns {Set}
- */
- async function deepReadDir(src) {
- const result = new Set
- try {
- const arr = await fs.readdir(src)
- /**
- * 获取所有文件的stat
- */
- const list = await Promise.all(arr.map(str => {
- return new Promise(async resolve => {
- const stat = await fs.stat(path.join(src, str))
- resolve(stat.isDirectory())
- })
- }))
- /**
- * 将文件添加到结果
- */
- for (const item of arr.filter((v, index) => !list[index]).map(i => path.join(src, i))) {
- result.add(item)
- }
- /**
- * 循环获取子目录的内容
- */
- const dirs = arr.filter((v, i) => list[i])
- for (const dir of dirs) {
- const res = await deepReadDir(path.join(src, dir))
- for (const item of res) {
- result.add(item)
- }
- }
- return result
- } catch (e) {
- console.error(e)
- return result
- }
- }
- start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement