UNPKG

6.86 kBJavaScriptView Raw
1const path = require( 'path' );
2const chalk = require( 'chalk' );
3
4// Fix rollup watcher chokidar path issue.
5const _tmpCwd = process.cwd();
6process.chdir( __dirname );
7
8// Require rollup.
9const rollup = require( 'rollup' );
10
11// Revert rollup watcher chokidar path issue fix.
12process.chdir( _tmpCwd );
13
14// Rollup dependencies.
15const rollupNodeResolve = require( 'rollup-plugin-node-resolve' );
16const rollupCommonJS = require( 'rollup-plugin-commonjs' );
17const babel = require( 'rollup-plugin-babel' );
18const Observable = require( 'zen-observable' );
19const minify = require('./rollup-minify');
20
21/**
22 * Returns current local time string.
23 *
24 * @returns {String}
25 */
26const currentLocalTime = function() {
27 return ( new Date() ).toLocaleTimeString();
28};
29
30const logPrefix = function() {
31 return chalk`{gray ${ currentLocalTime() }} {bold [JS]}`;
32};
33
34const formatError = function( error ) {
35 if ( error.formatted ) {
36 return chalk`{red ${error.formatted}}`;
37 }
38
39 if ( error.message ) {
40 return chalk`{red ${error.message}}`;
41 }
42
43 return error.toString();
44};
45
46const validate = function( {
47 source,
48 destination,
49 globals,
50 external,
51 mode = 'build',
52 includeBabelPolyfill = true,
53} ) {
54 if ( mode !== 'watch' && mode !== 'build' ) {
55 mode = 'build';
56 }
57
58 if ( includeBabelPolyfill === false || includeBabelPolyfill === 'false' ) {
59 includeBabelPolyfill = false;
60 } else {
61 includeBabelPolyfill = true;
62 }
63
64 return new Promise( function( resolve, reject ) {
65 if ( ! source ) {
66 return reject( chalk`${ logPrefix() } {red Source is required!}` );
67 }
68
69 if ( ! destination ) {
70 return reject( chalk`${ logPrefix() } {red Destination is required!}` );
71 }
72
73 return resolve();
74 } )
75 .then( () => ( {
76 source,
77 destination,
78 globals,
79 external,
80 mode,
81 includeBabelPolyfill,
82 } ) );
83};
84
85const rollupPluginPrependBabelPolyfill = function(source) {
86 return {
87 name: 'prepend-babel-polyfill',
88 transform(code, id) {
89 if (!String.prototype.endsWith.call(id, source)) {
90 return;
91 }
92
93 return {
94 code: 'import \'@runforest/js/babel-polyfill.js\';\n' + code,
95 };
96 },
97 };
98};
99
100const getPluginsOption = function( { source, includeBabelPolyfill } ) {
101 const plugins = [];
102
103 // Use the Node.js resolution algorithm with Rollup
104 plugins.push(rollupNodeResolve({
105 // some package.json files have a `browser` field which
106 // specifies alternative files to load for people bundling
107 // for the browser. If that's you, use this option, otherwise
108 // pkg.browser will be ignored
109 browser: true,
110 // Additional options
111 customResolveOptions: {
112 // Set node_modules as module directory
113 moduleDirectory: 'node_modules',
114 },
115 }));
116
117 // Convert CommonJS modules to ES2015
118 plugins.push(rollupCommonJS({
119 // Convert only imported npm modules
120 include: 'node_modules/**',
121 }));
122
123 if (includeBabelPolyfill) {
124 // Prepends import '@babel/polyfill';
125 plugins.push(rollupPluginPrependBabelPolyfill(source));
126 }
127
128 // Babel is a toolchain that is mainly used to convert ECMAScript 2015+
129 // code into a backwards compatible version of JavaScript in current
130 // and older browsers or environments
131 plugins.push(babel({
132 // Do not transform npm modules
133 exclude: 'node_modules/**',
134 presets: [[
135 // @babel/preset-env is a smart preset that allows you to use
136 // the latest JavaScript without needing to micromanage which
137 // syntax transforms (and optionally, browser polyfills) are
138 // needed by your target environment(s).
139 require( '@babel/preset-env' ),
140 {
141 // The starting point where the config search for
142 // browserslist will start, and ascend to the system root
143 // until found
144 configPath: source,
145 // Disable transformation of ES6 module syntax because that
146 // is what rollup do
147 modules: false,
148 // @babel/polyfill insertion is handled by the plugin above
149 useBuiltIns: 'entry',
150 },
151 ]],
152 }));
153
154 // ECMAScript 2015+ minifier
155 plugins.push(minify());
156
157 return plugins;
158};
159
160const getInputOptions = function( config ) {
161 const { source, external } = config;
162
163 return {
164 input: source,
165 external: external,
166 plugins: getPluginsOption( config ),
167 };
168};
169
170const getOutputOptions = function( source, destination, globals ) {
171 return {
172 format: 'iife',
173 file: path.resolve( destination, path.basename( source ) ),
174 globals: globals,
175 sourcemap: true,
176 };
177};
178
179module.exports = function( config ) {
180 return new Observable( function( observer ) {
181 validate( config )
182 .then( function( config ) {
183 return new Promise( function( resolve, reject ) {
184 const { mode } = config;
185
186 if ( 'watch' !== mode ) {
187 return resolve( config );
188 }
189
190 const { source, destination, globals } = config;
191
192 const watcher = rollup.watch( Object.assign(
193 getInputOptions( config ),
194 {
195 output: getOutputOptions( source, destination, globals ),
196 watch: {
197 chokidar: true,
198 exclude: 'node_modules/**',
199 },
200 }
201 ) );
202
203 let waitingMessageTimeout;
204
205 watcher.on( 'event', function( event ) {
206 clearTimeout( waitingMessageTimeout );
207
208 switch ( event.code ) {
209 case 'START':
210 observer.next( 'Start compiling...' );
211
212 break;
213
214 case 'BUNDLE_START':
215 observer.next(
216 chalk`Compiling {blue ${ event.input }}...`
217 );
218 break;
219
220 case 'BUNDLE_END':
221 const destinations = event.output
222 .map( ( dest ) => path.relative( process.cwd(), dest ) )
223 .join( ', ' );
224
225 observer.next(
226 chalk`{blue ${ event.input }} is compiled to ` +
227 chalk`{green ${ destinations }}.`
228 );
229 break;
230
231 case 'END':
232 waitingMessageTimeout = setTimeout( () => {
233 observer.next( 'Waiting...' );
234 }, 2000 );
235 break;
236
237 case 'ERROR':
238 if ( event.error ) {
239 observer.next( formatError( event.error ) );
240 } else {
241 observer.next(
242 chalk`{red Unknown error!}\n` +
243 formatError( event )
244 );
245 }
246 break;
247
248 default:
249 watcher.close();
250 if ( event.error ) {
251 reject( event.error );
252 } else {
253 reject(
254 chalk`{red Unknown unrecoverable error!}\n` +
255 formatError( event )
256 );
257 }
258
259 break;
260 }
261 } );
262 } );
263 } )
264 .then( function( config ) {
265 const { mode } = config;
266
267 if ( 'build' !== mode ) {
268 return Promise.resolve( config );
269 }
270
271 const { source, destination, globals } = config;
272
273 return rollup.rollup( getInputOptions( config ) )
274 .then(
275 ( bundle ) =>
276 bundle.write( getOutputOptions( source, destination, globals ) )
277 );
278 } )
279 .then( function() {
280 observer.complete();
281 } )
282 .catch( function( error ) {
283 observer.error( error );
284 } );
285 } );
286};