UNPKG

8.77 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3const Path = require('path');
4const Json5 = require('json5');
5const Util = require('util');
6const FS = require('./fs2');
7const Rollup = require('rollup');
8const nodeResolve = require('rollup-plugin-node-resolve');
9const commonjs = require('rollup-plugin-commonjs');
10const babel = require('rollup-plugin-babel');
11const del = require('del');
12const builtinModules = require('builtin-modules');
13const {ArgumentParser} = require('argparse');
14const npm = require('npm');
15const {codeFrameColumns} = require('@babel/code-frame');
16const Chalk = require('chalk');
17
18// secret multi-rollup: https://github.com/rollup/rollup/issues/863#issuecomment-306061779 https://github.com/rollup/rollupjs.org/blob/b9fe8623f2d958fd15a524235a6eb05900c2b9f1/rollup.config.js
19
20// https://medium.com/@tarkus/how-to-build-and-publish-es6-modules-today-with-babel-and-rollup-4426d9c7ca71
21process.on('unhandledRejection', dump);
22
23
24function* findPlugins(searchString, obj, path) {
25 if(obj[searchString]) {
26 for(let i=0; i<obj[searchString].length; ++i) {
27 if(Array.isArray(obj[searchString][i])) {
28 yield [[...path, searchString, i, 0], obj[searchString][i][0]];
29 } else {
30 yield [[...path, searchString, i], obj[searchString][i]];
31 }
32 }
33 }
34 for(let key of Object.keys(obj)) {
35 if(key !== searchString && typeof obj[key] === 'object') {
36 let newPath = [...path, key];
37 for(let x of findPlugins(searchString, obj[key], newPath)) {
38 yield x;
39 }
40 }
41 }
42}
43
44// https://github.com/babel/babel/blob/70361f12005e9ce0f9b961097006ff89f07c34a7/packages/babel-preset-env/src/normalize-options.js#L45-L46
45const normalizePluginName = (plugin) => plugin.replace(/^babel-plugin-/, "");
46
47(async () => {
48 const argparse = new ArgumentParser({
49 // version: pkg.version,
50 // description: pkg.description,
51 addHelp: true,
52 });
53
54
55 argparse.addArgument(['-w', '--watch'], {
56 help: "Watch source files for changes",
57 dest: 'watch',
58 action: 'storeTrue',
59 });
60
61 const cliArgs = argparse.parseArgs();
62
63 const pkgFile = await FS.closest('package.json');
64 const pkgJson = await FS.readText(pkgFile);
65 const pkg = Json5.parse(pkgJson);
66 const mainFields = ['browser', 'module', 'main']; // https://webpack.js.org/configuration/resolve/#resolve-mainfields
67 const pkgDeps = objectKeys(pkg.dependencies).concat(objectKeys(pkg.peerDependencies));
68 const pkgDir = Path.dirname(pkgFile);
69 const distDir = Path.join(pkgDir, 'dist');
70 const babelRcFile = Path.join(pkgDir, '.babelrc');
71
72 // await del(distDir);
73 // TODO: only delete the files that weren't re-written?
74
75 let babelOptions;
76 let useBabel = await FS.exists(babelRcFile);
77 if(useBabel) {
78 babelOptions = Json5.parse(await FS.readText(babelRcFile));
79 // fixme: a bunch of BS to solve this: https://stackoverflow.com/q/47660601/65387
80 let pluginPaths = findPlugins('plugins', babelOptions, []);
81 // dump('pluginPaths',Array.from(pluginPaths));
82 for(let [path,plug] of pluginPaths) {
83 let plugMod;
84 // TODO: check if path is relative
85 try {
86 plugMod = require.resolve(plug)
87 } catch(_) {
88 try {
89 plugMod = require.resolve(`babel-plugin-${plug}`);
90 } catch(_) {
91 plugMod = plug;
92 }
93 }
94 setValue(babelOptions, path, plugMod);
95 }
96
97 let presetPaths = findPlugins('presets', babelOptions, []);
98 // dump('presetPaths',Array.from(presetPaths));
99
100 for(let [path,preset] of presetPaths) {
101 let plugMod;
102 // TODO: check if path is relative
103 try {
104 plugMod = require.resolve(preset)
105 } catch(_) {
106 try {
107 plugMod = require.resolve(`babel-preset-${preset}`);
108 } catch(_) {
109 plugMod = preset;
110 }
111 }
112 setValue(babelOptions, path, plugMod);
113 }
114 }
115
116 // dump('babelOptions',babelOptions);
117
118 // process.exit();
119
120 let rollupConfigs = [];
121
122 await forAll(mainFields, field => forAll(toArray(pkg[field]), async filename => {
123 const srcFile = Path.resolve(pkgDir, filename);
124
125 if(!(await FS.exists(srcFile))) {
126 console.log(`Entry file does not exist: ${srcFile}`);
127 delete pkg[field];
128 return;
129 }
130
131 const distFile = `${field}.js`;
132 const distPath = Path.join(distDir, distFile);
133 pkg[field] = distFile;
134
135 let rollupOptions = {
136 input: srcFile,
137 external: field === 'main' ? [...pkgDeps, ...builtinModules] : pkgDeps,
138 name: pkg.name,
139 plugins: [
140 nodeResolve({
141 jail: pkgDir,
142 // jsnext: true,
143 }),
144 commonjs(),
145 ],
146 output: {
147 file: distPath,
148 format: field === 'main' ? 'cjs' : 'es', // since browsers are now starting to support ES-modules, assume both "browser" and "module" mean ES-modules.
149 sourcemap: true,
150 },
151 watch: {
152 chokidar: true,
153 }
154 };
155
156 if(useBabel) {
157 rollupOptions.plugins.unshift(babel({
158 ...babelOptions,
159 babelrc: false,
160 }));
161 }
162
163 // dump(babelOptions);
164 // dump(inputOptions);
165
166
167
168 rollupConfigs.push(rollupOptions);
169 }));
170
171 // dump('rollupConfigs',rollupConfigs);
172
173 delete pkg.devDependencies;
174 delete pkg.scripts;
175 delete pkg.eslintConfig;
176 delete pkg.babel;
177
178 await FS.writeText(Path.join(distDir, 'package.json'), JSON.stringify(pkg, null, 2));
179
180 // TODO: copy over LICENSE, LICENSE.txt, LICENSE.md etc.
181 // and README.md
182
183 if(cliArgs.watch) {
184 // FIXME: this watcher *really* sucks under Ubuntu+PhpStorm, even with chokidar: true
185 // Easier to create a "File Watcher" from inside PhpStorm
186 let watcher = Rollup.watch(rollupConfigs);
187
188 watcher.on('event', ev => {
189 // https://rollupjs.org/#rollup-watch
190 switch(ev.code) {
191 case 'START':
192 break;
193 case 'END':
194 break;
195 case 'BUNDLE_START':
196 // let now = new Date();
197 // process.stdout.write(`${Chalk.grey(`[${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}]`)} compiling...`);
198 break;
199 case 'BUNDLE_END':
200 // process.stdout.write(` done\n`);
201 break;
202 default:
203 dump(ev);
204 break;
205 }
206 });
207 } else {
208 try {
209 // let bundle = await Rollup.write(rollupConfigs);
210 // await bundle.write(outputOptions);
211
212 // rollup decided it would be too convenient to have a method that does this...? https://github.com/rollup/rollup/issues/863#issuecomment-349426989
213 await Promise.all(rollupConfigs.map(({output, ...input}) => Rollup.rollup(input).then(bundle => bundle.write(output))));
214 } catch(err) {
215 // console.dir(err);
216 // console.log(Reflect.ownKeys(err));
217 console.log(Chalk.red(err.message));
218 if(err.id && err.loc) {
219 let srcCode = await FS.readText(err.id);
220 console.log(codeFrameColumns(srcCode, {
221 start: {
222 line: err.loc.line,
223 column: err.loc.column + 1,
224 }
225 }, {forceColor: true}));
226 }// else {
227 dump(err);
228 //}
229 }
230 }
231})();
232
233function objectKeys(obj) {
234 return obj ? Object.keys(obj) : [];
235}
236
237function toArray(x) {
238 if(x == null) return [];
239 if(Array.isArray(x)) return x;
240 return [x];
241}
242
243function dump(...args) {
244 console.log(...args.map(o => Util.inspect(o, {colors: true, depth: 10})));
245}
246
247function forAll(iterable, callback) {
248 let promises = [];
249 for(let x of iterable) {
250 promises.push(callback(x));
251 }
252 return Promise.all(promises);
253}
254
255function setValue(obj, path, value) {
256 const end = path.length - 1;
257 for(let i=0; i<end; ++i) {
258 obj = obj[path[i]];
259 }
260 obj[path[end]] = value;
261}