UNPKG

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