UNPKG

11.8 kBJavaScriptView Raw
1const { execSync } = require('child_process');
2const path = require('path');
3const { stat, readFile: readFileCallback, unlink } = require('fs');
4const babel = require('babel-core');
5const chokidar = require('chokidar');
6const glob = require('glob');
7const readdir = require('fs-readdir-recursive');
8const slash = require('slash');
9const Lock = require('lock');
10const Queue = require('promise-queue');
11const promiseCallback = require('promise-callback-factory').default;
12const Logger = require('nightingale').default;
13const copyChmod = require('./utils/copyChmod');
14const copyFile = require('./utils/copyFile');
15const writeFile = require('./utils/writeFile');
16const destFromSrc = require('./utils/destFromSrc');
17const plugins = require('./plugins');
18const createBabelOptions = require(`./babel-options`);
19const { logger: parentLogger } = require('./logger');
20const Task = require('./cli-spinner');
21
22const readFile = filepath => promiseCallback(done => readFileCallback(filepath, done));
23
24
25const queue = new Queue(40, Infinity);
26
27function toErrorStack(err) {
28 if (err._babel && err instanceof SyntaxError) {
29 return `${err.name}: ${err.message}\n${err.codeFrame}`;
30 } else {
31 return err.stack;
32 }
33}
34
35module.exports = function transpile(pobrc, cwd, src, outFn, envs, watch, options) {
36 const srcBasePath = src;
37 const srcFiles = glob.sync(src, { cwd });
38 const _lock = Lock();
39 const lock = resource => new Promise(resolve => _lock(resource, release => resolve(() => release()())));
40 let task = new Task(`build ${src}`);
41
42 let logger = parentLogger.child('build', 'build');
43 const watchLogger = parentLogger.child('watch', 'watch');
44 let watching = false;
45
46 const timeBuildStarted = Date.now();// logger.infoTime('building ' + src);
47 logger.debug('envs', { envs });
48
49 const optsManagers = {};
50
51 envs.forEach(env => {
52 const optsManager = new babel.OptionManager;
53
54 optsManager.mergeOptions({
55 options: createBabelOptions(env, pobrc.react, options),
56 alias: 'base',
57 loc: cwd,
58 });
59
60 optsManagers[env] = optsManager;
61 });
62
63
64 function handle(filename) {
65 return promiseCallback(done => stat(filename, done))
66 .catch(err => {
67 console.log(err);
68 process.exit(1);
69 })
70 .then(stat => {
71 if (stat.isDirectory(filename)) {
72 let dirname = filename;
73
74 const allSrcFiles = readdir(dirname);
75 const allAllowedDestFiles = allSrcFiles.map(relative => destFromSrc(relative));
76
77 envs.forEach(env => {
78 const out = outFn(env);
79 const envAllFiles = readdir(out);
80
81 const diff = envAllFiles.filter(path => !allAllowedDestFiles.includes(path.replace(/.map$/, '')));
82 if (diff.length) {
83 logger.debug(`${out}: removing: ${diff.join(',')}`);
84 execSync('rm -Rf ' + diff.map(filename => path.join(out, filename)).join(' '));
85 }
86 });
87
88 return Promise.all(allSrcFiles.map(filename => {
89 let src = path.join(dirname, filename);
90 return handleFile(src, filename);
91 }));
92 } else {
93 }
94 });
95 }
96
97 function handleFile(src, relative) {
98 if (_lock.isLocked(relative)) logger.debug(relative + ' locked, waiting...');
99 return lock(relative).then(release => {
100 return queue.add(() => {
101 if (babel.util.canCompile(relative, options && options.babelExtensions)) {
102 const subtask = task.subtask('compiling: ' + relative);
103 logger.debug('compiling: ' + relative);
104
105 return Promise.resolve(src)
106 .then(src => readFile(src))
107 .then(content => {
108 return Promise.all(envs.map(env => {
109 const dest = path.join(outFn(env), destFromSrc(relative));
110
111 const opts = optsManagers[env].init({ filename: relative });
112 opts.babelrc = false;
113 opts.sourceMap = true;
114 opts.sourceFileName = slash(path.relative(dest + "/..", src));
115 opts.sourceMapTarget = path.basename(relative);
116
117 return Promise.resolve(content)
118 .then(content => babel.transform(content, opts))
119 .catch(watch && (err => {
120 console.log(toErrorStack(err));
121
122 return { map: null, code: 'throw new Error("Syntax Error");' };
123 }))
124 .then(data => {
125 const mapLoc = dest + ".map";
126 data.code = data.code + "\n//# sourceMappingURL=" + path.basename(mapLoc);
127 return writeFile(dest, data.code)
128 .then(() => Promise.all([
129 copyChmod(src, dest),
130 writeFile(mapLoc, JSON.stringify(data.map)),
131 ]));
132 });
133 }));
134 })
135 .then(() => {
136 logger[watching ? 'success' : 'debug']('compiled: ' + relative);
137 })
138 .then(() => release(), err => { release(); throw err; })
139 .then(() => subtask.done(), err => { subtask.done(); throw err; });
140 } else {
141 const extension = path.extname(relative).substr(1);
142 const plugin = plugins.findByExtension(extension);
143 if (plugin) {
144 const subtask = task.subtask(plugin.extension + ': ' + relative);
145 logger.debug(plugin.extension + ': ' + relative);
146 const destRelative = destFromSrc(relative, plugin);
147 return Promise.resolve(src)
148 .then(src => readFile(src))
149 .then(content => plugin.transform(content, { src, relative, srcBasePath }))
150 .catch(watch && (err => {
151 console.log(toErrorStack(err));
152
153 return { map: null, code: 'throw new Error("Syntax Error");' };
154 }))
155 .then(result => {
156 if (!result) {
157 // plugin returned nothing, remove.
158 return Promise.all(envs.map(env => (
159 Promise.all([
160 promiseCallback(done => unlink(dest, done)).catch(() => {}),
161 promiseCallback(done => unlink(`${dest}.map`, done)).catch(() => {}),
162 ])
163 )));
164 }
165
166 const { code, map } = result;
167 return Promise.all(envs.map(env => {
168 const dest = path.join(outFn(env), destRelative);
169 const mapLoc = dest + ".map";
170 return writeFile(dest, code)
171 .then(() => Promise.all([
172 copyChmod(src, dest),
173 map && writeFile(mapLoc, JSON.stringify(map)),
174 ]));
175 }))
176 })
177 .then(() => release(), err => { release(); throw err; })
178 .then(() => subtask.done(), err => { subtask.done(); throw err; });
179 } else {
180 const subtask = task.subtask('copy: ' + relative);
181 logger.debug('copy: ' + relative);
182 return Promise.all(envs.map(env => {
183 const out = outFn(env);
184 const dest = path.join(out, relative);
185 return copyFile(src, dest).then(() => copyChmod(src, dest));
186 }))
187 .then(() => release(), err => { release(); throw err; })
188 .then(() => subtask.done(), err => { subtask.done(); throw err; });
189 }
190 }
191 });
192 }).catch(err => {
193 console.log(toErrorStack(err));
194 if (!watch) {
195 process.exit(1);
196 }
197 });
198 }
199
200 if (watch) {
201 process.nextTick(() => {
202 srcFiles.forEach(dirname => {
203 const watcher = chokidar.watch(dirname, {
204 persistent: true,
205 ignoreInitial: true
206 });
207
208 function handleChange(filename) {
209 let relative = path.relative(dirname, filename) || filename;
210 watchLogger.debug('changed: ' + relative);
211 task.subtask('changed: ' + relative);
212 handleFile(filename, relative)
213 .catch(err => {
214 console.log(err.stack);
215 })
216 .then(() => watch.emit('changed', filename));
217 }
218
219 watcher.on('add', handleChange);
220 watcher.on('change', handleChange);
221 watcher.on('unlink', filename => {
222 let relative = path.relative(dirname, filename) || filename;
223 watchLogger.debug('unlink: ' + relative);
224 const subtask = task.subtask('delete: ' + relative);
225 if (_lock.isLocked(relative)) watchLogger.debug(relative + ' locked, waiting...');
226 lock(relative).then(release => {
227 return Promise.all(envs.map(env => {
228 const dest = path.join(outFn(env), destFromSrc(relative));
229
230 return Promise.all([
231 promiseCallback(done => unlink(dest, done)).catch(() => {}),
232 promiseCallback(done => unlink(`${dest}.map`, done)).catch(() => {}),
233 ]);
234 }))
235 .then(() => release())
236 .then(() => watch.emit('changed', filename))
237 .then(() => subtask.done());
238 });
239 });
240 });
241 });
242 }
243
244 return Promise.all(srcFiles.map(filename => handle(filename)))
245 .then(() => {
246 logger.infoSuccessTimeEnd(timeBuildStarted, 'build finished');
247 task.succeed();
248 if (watch) {
249 task = new Task('watch');
250 logger.info('watching');
251 watching = true;
252 logger = watchLogger;
253 }
254 })
255 .catch(err => {
256 console.log(err.stack);
257 process.exit(1);
258 });
259};