UNPKG

8.81 kBJavaScriptView Raw
1#!/usr/bin/env node
2const FileSystem = require('fs');
3const Path = require('path');
4const {ArgumentParser} = require('argparse');
5const pkg = require('./package.json');
6const Babel = require('babel-core');
7const {promisify, readDirR, copyFile, log, splice, mapToObject, mergeDeep, getFiles, readDir, fileStat, readText, forAll} = require('./util');
8const mkdirp = promisify(require('mkdirp'));
9const writeFile = promisify(FileSystem.writeFile);
10const BabelTypes = require('babel-types');
11const traverseAst = require('babel-traverse').default;
12const babelGenerator = require('babel-generator').default;
13const babelTemplate = require('babel-template');
14const Babylon = require('babylon');
15const Chalk = require('chalk');
16
17let parser = new ArgumentParser({
18 version: pkg.version,
19 description: pkg.description,
20 addHelp: true,
21});
22
23parser.addArgument('pkgDir', {
24 help: "Directory with package.json",
25 metavar: "package-dir",
26 defaultValue: '.',
27});
28
29parser.addArgument(['-s', '--src-dir'], {
30 help: "Source directory to bundle up",
31 metavar: "src-dir",
32 dest: 'srcDir',
33});
34
35parser.addArgument(['-d', '--out-dir'], {
36 help: "Output directory",
37 metavar: "out-dir",
38 dest: 'outDir',
39});
40
41parser.addArgument(['-t', '--target'], {
42 help: "Target environment",
43 metavar: "target",
44 dest: 'target',
45 defaultValue: 'node:6,webpack:2'
46});
47
48parser.addArgument(['--index'], {
49 help: "Generate an index file for each directory where it is missing",
50 // defaultValue: true,
51 // type: x => x !== 'false',
52 // storeConst: 'bacon',
53 // action: 'storeFalse',
54 // constant: true,
55 // defaultValue: false,
56 // dest: 'index',
57 dest: 'index',
58 defaultValue: 'shallow', // or "deep" or "false"
59});
60
61
62let args = parser.parseArgs();
63
64
65const SRC_DIR = args.srcDir || Path.join(args.pkgDir, 'src');
66// TODO: read package.json to make a better guess about the dist dir?
67const DIST_DIR = args.outDir || Path.join(SRC_DIR, '..', 'dist');
68
69
70// log(srcDir,outDir);return;
71
72// console.log(require('util').inspect(args,{colors:true}));return;
73
74let sourceFiles = readDirR(SRC_DIR);
75sourceFiles.sort();
76
77// console.log(require('util').inspect(files,{colors:true}));return;
78const transformFile = promisify(Babel.transformFile);
79// const transpile = promisify(Babel.transform);
80
81const babelEnvs = {
82 node: {
83 plugins: [
84 require.resolve('babel-plugin-dynamic-import-node'),
85 ],
86 presets: [
87 [
88 require.resolve('babel-preset-env'),
89 {
90 modules: 'commonjs',
91 loose: true,
92 targets: {
93 node: 6
94 }
95 }
96 ]
97 ],
98 },
99 web: {
100 plugins: [
101 // require.resolve('babel-plugin-dynamic-import-webpack'), //needed for webpack 1
102 ],
103 presets: [
104 [
105 require.resolve('babel-preset-env'),
106 {
107 modules: false,
108 loose: true,
109 targets: {
110 browsers: [
111 "> 1%",
112 "last 2 Firefox versions",
113 "last 2 Chrome versions",
114 "last 2 Edge versions",
115 "last 2 Safari versions",
116 "Firefox ESR",
117 "IE >= 8"
118 ]
119 }
120 }
121 ]
122 ],
123 },
124};
125
126// let outDirs = [...new Set(files.map(f => Path.dirname(f)))];
127
128
129async function processDir(inputDir) {
130 let srcEntries = await readDir(inputDir);
131
132 let hasIndex = srcEntries.map(p => Path.parse(p).name).includes('index');
133 let indexLines = [];
134 // log(inputDir,hasIndex);
135
136 await forAll(srcEntries, async srcFile => {
137 let srcStat = await fileStat(srcFile);
138
139 if(srcStat.isDirectory()) {
140 processDir(srcFile);
141 } else {
142 let srcParsed = Path.parse(srcFile);
143 let destFileBase = srcParsed.base.replace(/\.(web|node)\./, '.');
144
145 if(/\.jsx?/.test(srcFile)) {
146 let fileContents = await readText(srcFile);
147
148 if(!hasIndex) {
149 let ast;
150 try {
151 ast = Babylon.parse(fileContents, {
152 sourceType: 'module',
153 plugins: [
154 // https://github.com/babel/babylon#plugins
155 'jsx',
156 'doExpressions',
157 'objectRestSpread',
158 'classProperties',
159 'exportExtensions',
160 'dynamicImport',
161 'functionBind',
162 ]
163 });
164 } catch(parseErr) {
165 console.log(`${Chalk.red(`Parse error in ${Chalk.underline(srcFile)}`)}`, parseErr.message);
166 return;
167 }
168
169
170 let destParsed = Path.parse(destFileBase);
171 let defaultId = destParsed.name; // TODO: make sure this is a valid JS dentifier
172 let srcString = JSON.stringify(`./${destParsed.name}`);
173
174
175 let hasDefaultExport = ast.program.body.some(x => x.type === 'ExportDefaultDeclaration');
176 let hasNamedExport = ast.program.body.some(x => x.type === 'ExportNamedDeclaration');
177
178
179 if(hasDefaultExport) {
180 indexLines.push(`export {default as ${defaultId}} from ${srcString};`);
181 }
182 if(hasNamedExport) {
183 // this is "shallow" -- export {* as FILE} from 'xxx'; would be deep
184 indexLines.push(`export * from ${srcString};`);
185 }
186 }
187
188 ['node', 'web'].forEach(async target => {
189
190 let result = transpile(target, fileContents, srcFile);
191
192 let destDir = Path.join(DIST_DIR, target, Path.relative(SRC_DIR, inputDir));
193 let destFile = Path.join(destDir, destFileBase);
194
195 await mkdirp(destDir);
196 await writeFile(destFile, result.code);
197 console.log(`compiled ${srcFile} -> ${destFile}`);
198 });
199 } else {
200 ['node', 'web'].forEach(async target => {
201 // TODO: remove code repeittion
202 let destDir = Path.join(DIST_DIR, target, Path.relative(SRC_DIR, inputDir));
203 let destFile = Path.join(destDir, destFileBase);
204
205 await mkdirp(destDir);
206 await copyFile(srcFile, destFile);
207 console.log(`copied ${srcFile} -> ${destFile}`);
208 });
209 }
210 }
211 });
212
213
214 if(indexLines.length) {
215 indexLines.push(`import * as __self__ from './index';`);
216 indexLines.push(`export default __self__;`);
217 let indexFileContents = indexLines.join('\n');
218
219 ['node', 'web'].forEach(async target => {
220 let destDir = Path.join(DIST_DIR, target, Path.relative(SRC_DIR, inputDir));
221 let destFile = Path.join(destDir, 'index.js');
222 let result = transpile(target, indexFileContents, destFile);
223 await mkdirp(destDir);
224 await writeFile(destFile, result.code);
225 console.log(`wrote ${destFile}`);
226 });
227 }
228}
229
230function transpile(target, fileContents, filename) {
231 const babelPlugins = [
232 require.resolve('babel-plugin-transform-function-bind'),
233 require.resolve('babel-plugin-syntax-do-expressions'),
234 require.resolve('babel-plugin-syntax-dynamic-import'),
235 require.resolve('babel-plugin-transform-class-properties'),
236 require.resolve('babel-plugin-transform-do-expressions'),
237 require.resolve('babel-plugin-transform-function-bind'),
238 require.resolve('babel-plugin-transform-object-rest-spread'),
239 require.resolve('babel-plugin-transform-react-jsx'),
240 // require.resolve('babel-plugin-transform-runtime'),
241 [require.resolve('babel-plugin-transform-define'), {'BUNDILIO_TARGET': target}],
242 ];
243
244 return Babel.transform(fileContents, mergeDeep({
245 ast: false,
246 code: true,
247 babelrc: false,
248 plugins: babelPlugins,
249 filename,
250 }, babelEnvs[target]));
251}
252
253processDir(SRC_DIR);
254
255// TODO: generate API documentation too?
\No newline at end of file