1 | const fs = require('fs')
|
2 | const path = require('path')
|
3 | const { EventEmitter } = require('events')
|
4 |
|
5 | const mkdirp = require('mkdirp')
|
6 | const minimatch = require('minimatch')
|
7 | const babel = require('@babel/core')
|
8 | const chokidar = require('chokidar')
|
9 |
|
10 | const DEFAULT_OPTIONS = {
|
11 | persistent: true,
|
12 | delete: true,
|
13 | babel: {},
|
14 | glob: '**/*.js'
|
15 | }
|
16 |
|
17 | const SOURCE_MAP_PREFIX = '//# sourceMappingURL=data:application/json;base64,'
|
18 |
|
19 | const btoa = str => new Buffer(str, 'binary').toString('base64')
|
20 |
|
21 | class BabelCompiler extends EventEmitter {
|
22 | constructor(src, dest, options) {
|
23 | super()
|
24 |
|
25 | const optionsWithDefault = Object.assign({}, DEFAULT_OPTIONS, options)
|
26 |
|
27 | this.src = src
|
28 | this.dest = dest
|
29 | this.options = optionsWithDefault
|
30 | this.ready = false
|
31 |
|
32 | mkdirp.sync(dest)
|
33 |
|
34 | const chokidarOptions = {
|
35 | cwd: src,
|
36 | persistent: optionsWithDefault.persistent
|
37 | }
|
38 |
|
39 | this.watcher = chokidar
|
40 | .watch('**/*.*', chokidarOptions)
|
41 | .on('all', (event, filepath) => this.onWatchEvent(event, filepath))
|
42 | .on('error', e => this.onError(e))
|
43 | .on('ready', () => this.onReady())
|
44 | }
|
45 |
|
46 | onWatchEvent(event, filepath = '.') {
|
47 | const srcPath = path.join(this.src, filepath)
|
48 | const destPath = path.join(this.dest, filepath)
|
49 |
|
50 | if (event === 'add' || event === 'change') {
|
51 | mkdirp.sync(path.dirname(destPath))
|
52 | if (minimatch(filepath, this.options.glob)) {
|
53 | this.compileJsFile(filepath, srcPath, destPath)
|
54 | } else {
|
55 | this.copyFile(filepath, srcPath, destPath)
|
56 | }
|
57 | }
|
58 |
|
59 | if (event === 'unlink') {
|
60 | if (this.options.delete) return
|
61 | fs.removeSync(destPath)
|
62 | this.emit('delete', filepath)
|
63 | }
|
64 |
|
65 | if (event === 'unlinkDir') {
|
66 | if (!this.options.delete) return
|
67 | fs.removeSync(destPath)
|
68 | }
|
69 | }
|
70 |
|
71 | onReady() {
|
72 | this.emit('ready')
|
73 | }
|
74 |
|
75 | onError(e) {
|
76 | this.emit('error', e)
|
77 | }
|
78 |
|
79 | compileJsFile(filepath, srcPath, destPath) {
|
80 | const babelOptions = this.options.sourceMaps
|
81 | ? {
|
82 | ...this.options.babel,
|
83 | sourceMaps: true,
|
84 | sourceFileName: srcPath
|
85 | }
|
86 | : this.options.babel
|
87 |
|
88 | let result
|
89 | try {
|
90 | result = babel.transformFileSync(srcPath, babelOptions)
|
91 | } catch (error) {
|
92 | this.emit('error', filepath, error)
|
93 | return
|
94 | }
|
95 |
|
96 | let content
|
97 | if (this.options.sourceMaps) {
|
98 | const sourceMap = btoa(JSON.stringify(result.map))
|
99 | content = `${result.code}\n${SOURCE_MAP_PREFIX}${sourceMap}`
|
100 | } else {
|
101 | content = result.code
|
102 | }
|
103 |
|
104 | fs.writeFileSync(destPath, content)
|
105 | this.emit('success', filepath)
|
106 | }
|
107 |
|
108 | copyFile(filepath, srcPath, destPath) {
|
109 | fs.copyFileSync(srcPath, destPath)
|
110 | this.emit('copy', filepath)
|
111 | }
|
112 |
|
113 | close() {
|
114 | this.watcher.close()
|
115 | this.removeAllListeners()
|
116 | }
|
117 | }
|
118 |
|
119 | module.exports = BabelCompiler
|