| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- /* jshint node: true, esnext: true */
- 'use strict';
- const KNOWN_OPTIONS = {
- build: {
- string: [
- 'formats'
- ],
- boolean: [
- 'watch'
- ],
- default: {
- formats: 'amd',
- watch: false
- }
- }
- };
- const fs = require( 'fs' );
- const path = require( 'path' );
- const gulp = require( 'gulp' );
- const del = require( 'del' );
- const merge = require( 'merge-stream' );
- const gulpMirror = require( 'gulp-mirror' );
- const gulpWatch = require( 'gulp-watch' );
- const gutil = require( 'gulp-util' );
- const minimist = require( 'minimist' );
- const utils = require( './utils' );
- const options = minimist( process.argv.slice( 2 ), KNOWN_OPTIONS[ process.argv[ 2 ] ] );
- module.exports = ( config ) => {
- const distDir = path.join( config.ROOT_DIR, config.DIST_DIR );
- const tasks = {
- /**
- * Removes the dist directory.
- */
- clean() {
- return del( distDir );
- },
- src: {
- /**
- * Returns a stream of all source files.
- *
- * @param {Boolean} [watch] Whether the files should be watched.
- * @returns {Stream}
- */
- all( watch ) {
- return merge( tasks.src.main( watch ), tasks.src.ckeditor5( watch ), tasks.src.packages( watch ) );
- },
- /**
- * Returns a stream with just the main file (`ckeditor5/ckeditor.js`).
- *
- * @param {Boolean} [watch] Whether to watch the files.
- * @returns {Stream}
- */
- main( watch ) {
- const glob = path.join( config.ROOT_DIR, 'ckeditor.js' );
- return gulp.src( glob )
- .pipe( watch ? gulpWatch( glob ) : utils.noop() );
- },
- /**
- * Returns a stream of all source files from CKEditor 5.
- *
- * @param {Boolean} [watch] Whether to watch the files.
- * @returns {Stream}
- */
- ckeditor5( watch ) {
- const glob = path.join( config.ROOT_DIR, '@(src|tests)', '**', '*.js' );
- return gulp.src( glob )
- .pipe( watch ? gulpWatch( glob ) : utils.noop() )
- .pipe( utils.wrapCKEditor5Package() );
- },
- /**
- * Returns a stream of all source files from CKEditor 5 dependencies.
- *
- * @param {Boolean} [watch] Whether to watch the files.
- * @returns {Stream}
- */
- packages( watch ) {
- // Find all CKEditor5 package directories. Resolve symlinks so we watch real directories
- // in order to workaround https://github.com/paulmillr/chokidar/issues/419.
- const dirs = fs.readdirSync( path.join( config.ROOT_DIR, 'node_modules' ) )
- // Look for ckeditor5-* directories.
- .filter( ( fileName ) => fileName.indexOf( 'ckeditor5-' ) === 0 )
- // Resolve symlinks and keep only directories.
- .map( ( fileName ) => {
- let filePath = path.join( config.ROOT_DIR, 'node_modules', fileName );
- let stat = fs.lstatSync( filePath );
- if ( stat.isSymbolicLink() ) {
- filePath = fs.realpathSync( filePath );
- stat = fs.lstatSync( filePath );
- }
- if ( stat.isDirectory() ) {
- return filePath;
- }
- // Filter...
- return false;
- } )
- // ...those out.
- .filter( ( filePath ) => filePath );
- const streams = dirs.map( ( dirPath ) => {
- const glob = path.join( dirPath, '@(src|tests)', '**', '*.js' );
- // Use parent as a base so we get paths starting with 'ckeditor5-*/src/*' in the stream.
- const baseDir = path.parse( dirPath ).dir;
- const opts = { base: baseDir };
- return gulp.src( glob, opts )
- .pipe( watch ? gulpWatch( glob, opts ) : utils.noop() );
- } );
- return merge.apply( null, streams )
- .pipe( utils.unpackPackages() );
- }
- },
- /**
- * The main build task which is capable of copying, watching, processing and writing all files
- * to the `dist/` directory.
- *
- * @returns {Stream}
- */
- build() {
- //
- // NOTE: Error handling in streams is hard.
- //
- // Most likely this code isn't optimal, but it's a result of 8h spent on search
- // for a solution to the ruined pipeline whenever something throws.
- //
- // Most important fact is that when dest stream emits an error the src stream
- // unpipes it. Problem is when you start using tools like multipipe or gulp-mirror,
- // because you lose control over the graph of the streams and you cannot reconnect them
- // with a full certainty that you connected them correctly, since you'd need to know these
- // libs internals.
- //
- // BTW. No, gulp-plumber is not a solution here because it does not affect the other tools.
- //
- // Hence, I decided that it'll be best to restart the whole piece. However, I wanted to avoid restarting the
- // watcher as it sounds like something heavy.
- //
- // The flow looks like follows:
- //
- // 1. codeStream
- // 2. inputStream
- // 3. conversionStream (may throw)
- // 4. outputStream
- //
- // The input and output streams allowed me to easier debug and restart everything. Additionally, the output
- // stream is returned to Gulp so it must be stable. I decided to restart also the inputStream because when conversionStream
- // throws, then inputStream gets paused. Most likely it's possible to resume it, so we could pipe codeStream directly to
- // conversionStream, but it was easier this way.
- //
- // PS. The assumption is that all errors thrown somewhere inside conversionStream are forwarded to conversionStream.
- // Multipipe and gulp-mirror seem to work this way, so we get a single error emitter.
- const formats = options.formats.split( ',' );
- const codeStream = tasks.src.all( options.watch )
- .on( 'data', ( file ) => {
- gutil.log( `Processing '${ gutil.colors.cyan( file.path ) }'...` );
- } );
- const conversionStreamGenerator = utils.getConversionStreamGenerator( distDir );
- const outputStream = utils.noop();
- let inputStream;
- let conversionStream;
- startStreams();
- return outputStream;
- // Creates a single stream combining multiple conversion streams.
- function createConversionStream() {
- const formatPipes = formats.reduce( conversionStreamGenerator, [] );
- return gulpMirror.apply( null, formatPipes )
- .on( 'error', onError );
- }
- // Handles error in the combined conversion stream.
- // If we don't watch files, make sure that the process terminates ASAP. We could forward the error
- // to the output, but there may be some data in the pipeline and our error could be covered
- // by dozen of other messages.
- // If we watch files, then clean up the old streams and restart the combined conversion stream.
- function onError() {
- if ( !options.watch ) {
- process.exit( 1 );
- return;
- }
- unpipeStreams();
- gutil.log( 'Restarting...' );
- startStreams();
- }
- function startStreams() {
- inputStream = utils.noop();
- conversionStream = createConversionStream();
- codeStream
- .pipe( inputStream )
- .pipe( conversionStream )
- .pipe( outputStream );
- }
- function unpipeStreams() {
- codeStream.unpipe( inputStream );
- conversionStream.unpipe( outputStream );
- }
- }
- };
- gulp.task( 'build:clean', tasks.clean );
- gulp.task( 'build', [ 'build:clean' ], tasks.build );
- return tasks;
- };
|