Advertisement
Guest User

Untitled

a guest
Jul 19th, 2019
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.12 KB | None | 0 0
  1. const fs = require('fs').promises
  2. const { createWriteStream, createReadStream } = require('fs')
  3. const { Duplex, Readable, Writable, Transform } = require('stream')
  4. const path = require('path')
  5. const pipeline = require('util').promisify(require('stream').pipeline)
  6.  
  7. /**
  8. * 流默认配置
  9. */
  10. const STREAMCONFIG = { objectMode: true }
  11.  
  12. const income = Symbol()
  13. const transfer = Symbol()
  14.  
  15. /**
  16. * 工作流基类
  17. * 所有处理文件的方法都是此类的实例或者子类
  18. * 实例需要实现transform方法
  19. */
  20. class WorkThread extends Transform {
  21. constructor(opts) {
  22. super({ ...opts, ...STREAMCONFIG })
  23. }
  24. }
  25.  
  26. /**
  27. * 文件处理基类
  28. * 通过一个函数返回处理后的内容,Buffer格式
  29. */
  30. class FileTransfer extends Duplex {
  31. /**
  32. * 将单个文件所有内容拼接后交给回调函数处理
  33. * @param {function} cb 处理文件内容的回调函数
  34. */
  35. constructor(cb) {
  36. super()
  37. this[income] = Buffer.from('')
  38. this.transfer = cb
  39. }
  40. _write(buf, enc, callback) {
  41. this[income] = Buffer.concat([this[income], buf])
  42. callback()
  43. }
  44. _read() {
  45. this.push(this[income] ? this.transfer(this[income]) : null)
  46. this[income] = null
  47. }
  48. }
  49.  
  50. /**
  51. * 文件扫描
  52. * 提供文件信息的Map
  53. * filename 文件名,包含扩展名
  54. * path 文件相对路径
  55. * file 文件的读取流
  56. * name 文件名
  57. */
  58. class FileScanner extends Readable {
  59. /**
  60. * 扫描所有文件
  61. * @param {string} src 路径
  62. */
  63. constructor(src) {
  64. super(STREAMCONFIG)
  65.  
  66. if (typeof src !== 'string') {
  67. throw new Error('Invalid src')
  68. }
  69. this.src = src
  70. this.list = []
  71. this.index = 0
  72. }
  73. async _read() {
  74. /**
  75. * 第一次执行
  76. * 如果没有文件列表就先读取文件列表
  77. */
  78. if (!this.list.length) {
  79. const arr = await deepReadDir(this.src)
  80. if (!arr.size) {
  81. return this.push(null)
  82. }
  83. this.list = [...arr]
  84. }
  85.  
  86. if (this.index >= this.list.length) {
  87. return this.push(null)
  88. }
  89.  
  90. const info = new Map
  91. const filepath = this.list[this.index++]
  92. const { base, dir, name } = path.parse(filepath)
  93. info.set('filename', base)
  94. info.set('path', path.relative(this.src, dir))
  95. info.set('name', name)
  96.  
  97. info.set('file', createReadStream(filepath))
  98. this.push(info)
  99. }
  100. }
  101.  
  102. /**
  103. * 文件输出
  104. * 需要提供文件信息的Map
  105. * filename 文件名,包含扩展名
  106. * path 相对路径
  107. * file 文件读取流
  108. */
  109. class FileOuputer extends Writable {
  110. constructor(dest) {
  111. super(STREAMCONFIG)
  112. if (typeof dest !== 'string') {
  113. throw new Error('Invalid dest')
  114. }
  115. this.dest = dest
  116. }
  117. async _write(info, enc, callback) {
  118. /**
  119. * 创建输出文件的目录
  120. */
  121. try {
  122. await fs.mkdir(path.join(this.dest, info.get('path')), { recursive: true })
  123. } catch (e) {
  124. console.error(e)
  125. return callback(e)
  126. }
  127.  
  128. const output = createWriteStream(path.join(this.dest, info.get('path'), info.get('filename')))
  129. const dir = path.parse(output.path).dir
  130. console.log(path.relative(this.dest, output.path))
  131.  
  132. /**
  133. * 输出到目标目录
  134. */
  135. try {
  136. await pipeline(info.get('file'), output)
  137. } catch (e) {
  138. console.error(e)
  139. } finally {
  140. callback()
  141. }
  142. }
  143. }
  144.  
  145. /**
  146. * 处理mina文件,输出小程序4个文件
  147. */
  148. const minaWorker = new WorkThread({
  149. transform(chunk, enc, callback) {
  150. /**
  151. * 此方法只对vue和mina格式文件生效
  152. * 其余文件直接跳过
  153. */
  154. const filename = chunk.get('filename')
  155. if (!/\.(vue|mina)$/i.test(chunk.get('filename'))) {
  156. return callback(null, chunk)
  157. }
  158.  
  159. /**
  160. * 生成样式
  161. */
  162. const styleInfo = new Map(chunk.entries())
  163. .set('filetype', 'style')
  164. .set('filename', chunk.get('name') + '.wxss')
  165.  
  166. const styleStream = new FileTransfer(buf => {
  167. const str = buf.toString()
  168. const styleMatches = str.match(/<style.*?>([\s\S]*?)<\/style>/sm)
  169. if (!styleMatches) {
  170. return Buffer.from('')
  171. }
  172.  
  173. return Buffer.from(styleMatches[1].trim(), 'utf8')
  174. })
  175. styleInfo.set('file', chunk.get('file').pipe(styleStream))
  176. this.push(styleInfo)
  177.  
  178. /**
  179. * 生成页面模板
  180. */
  181. const wxmlInfo = new Map(chunk.entries())
  182. .set('filetype', 'wxml')
  183. .set('filename', chunk.get('name') + '.wxml')
  184.  
  185. const wxmlStream = new FileTransfer(buf => {
  186. const str = buf.toString()
  187. const matches = str.match(/<template.*?>([\s\S]*?)<\/template>/sm)
  188.  
  189. if (!matches) {
  190. return Buffer.from('')
  191. }
  192.  
  193. return Buffer.from(matches[1].trim(), 'utf8')
  194. })
  195. const wxmlReader = createReadStream(chunk.get('file').path)
  196. wxmlInfo.set('file', wxmlReader.pipe(wxmlStream))
  197. this.push(wxmlInfo)
  198.  
  199. /**
  200. * 生成js
  201. */
  202. const jsInfo = new Map(chunk.entries())
  203. .set('filetype', 'js')
  204. .set('filename', chunk.get('name') + '.js')
  205.  
  206. const jsStream = new FileTransfer(buf => {
  207. const str = buf.toString()
  208. const matches = str.match(/<script.*?>([\s\S]*?)<\/script>/sm)
  209.  
  210. if (!matches) {
  211. return Buffer.from('')
  212. }
  213.  
  214. return Buffer.from(matches[1].trim(), 'utf8')
  215. })
  216. const jsReader = createReadStream(chunk.get('file').path)
  217. jsInfo.set('file', jsReader.pipe(jsStream))
  218. this.push(jsInfo)
  219.  
  220. /**
  221. * 生成json
  222. */
  223. const configInfo = new Map(chunk.entries())
  224. .set('filetype', 'json')
  225. .set('filename', chunk.get('name') + '.json')
  226.  
  227. const jsonStream = new FileTransfer(buf => {
  228. const str = buf.toString()
  229. const matches = str.match(/<script.*?>([\s\S]*?)<\/script>/sm)
  230.  
  231. if (!matches) {
  232. return Buffer.from('{}')
  233. }
  234.  
  235. const jsonData = new Function('App', 'Page', 'Component', matches[1].replace(/((Page|App|Componet)\()/, 'return $1'))
  236.  
  237. function _g(obj) {
  238. return obj
  239. }
  240. const config = jsonData(_g, _g, _g).config
  241. if (!config) {
  242. return Buffer.from('{}', 'utf8')
  243. }
  244.  
  245. return Buffer.from(JSON.stringify(config), 'utf8')
  246. })
  247. const jsonReader = createReadStream(chunk.get('file').path)
  248. configInfo.set('file', jsReader.pipe(jsonStream))
  249. this.push(configInfo)
  250.  
  251. return callback()
  252. }
  253. })
  254.  
  255. /**
  256. * 处理
  257. */
  258. const copyWorker=new WorkThread({
  259. transform(info, enc, callback) {
  260.  
  261. }
  262. })
  263.  
  264. async function winnie(options) {
  265. console.time('used')
  266. const lines = new Set()
  267. .add(new FileScanner(options.get('src')))
  268. .add(minaWorker)
  269. .add(new FileOuputer(options.get('dest')))
  270.  
  271. try {
  272. await pipeline(...lines)
  273. } catch (e) {
  274. console.error(e)
  275. } finally {
  276. console.timeEnd('used')
  277. }
  278. }
  279.  
  280. function start() {
  281. const args = parseParams()
  282. winnie(args).catch(e => console.error(e.message))
  283. }
  284.  
  285. /**
  286. * 获取命令行参数
  287. * @returns {Map}
  288. */
  289. function parseParams() {
  290. let result = new Map
  291. let args = process.argv.slice(2)
  292. args.forEach((key, index, arr) => {
  293. if (/^\-{2}\S+/.test(key)) {
  294. // --name
  295. let name = key.match(/^\-+(\S+)$/)[1]
  296. if (typeof arr[index + 1] === 'string' && !/^\-{2}\S+/.test(arr[index + 1])) {
  297. result.set(name, arr[index + 1])
  298. // result[name] = arr[index + 1]
  299. } else {
  300. result.set(name, true)
  301. // result[name] = true
  302. }
  303. }
  304. })
  305. return result
  306. }
  307.  
  308. /**
  309. * 循环获取目录文件
  310. * @param {string} src 目录
  311. * @returns {Set}
  312. */
  313. async function deepReadDir(src) {
  314. const result = new Set
  315. try {
  316. const arr = await fs.readdir(src)
  317.  
  318. /**
  319. * 获取所有文件的stat
  320. */
  321. const list = await Promise.all(arr.map(str => {
  322. return new Promise(async resolve => {
  323. const stat = await fs.stat(path.join(src, str))
  324. resolve(stat.isDirectory())
  325. })
  326. }))
  327.  
  328. /**
  329. * 将文件添加到结果
  330. */
  331. for (const item of arr.filter((v, index) => !list[index]).map(i => path.join(src, i))) {
  332. result.add(item)
  333. }
  334.  
  335. /**
  336. * 循环获取子目录的内容
  337. */
  338. const dirs = arr.filter((v, i) => list[i])
  339. for (const dir of dirs) {
  340. const res = await deepReadDir(path.join(src, dir))
  341. for (const item of res) {
  342. result.add(item)
  343. }
  344. }
  345. return result
  346. } catch (e) {
  347. console.error(e)
  348. return result
  349. }
  350. }
  351.  
  352. start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement