utils.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /* jshint node: true, esnext: true */
  2. 'use strict';
  3. const path = require( 'path' );
  4. const gulp = require( 'gulp' );
  5. const rename = require( 'gulp-rename' );
  6. const babel = require( 'gulp-babel' );
  7. const gulpWatch = require( 'gulp-watch' );
  8. const gulpPlumber = require( 'gulp-plumber' );
  9. const gutil = require( 'gulp-util' );
  10. const multipipe = require( 'multipipe' );
  11. const stream = require( 'stream' );
  12. const sep = path.sep;
  13. const utils = {
  14. /**
  15. * Returns a stream of files matching the given glob pattern.
  16. *
  17. * @param {String} root The root directory.
  18. * @param {String} glob The glob pattern.
  19. * @param {Boolean} [watch] Whether to watch the files.
  20. * @returns {Stream}
  21. */
  22. src( root, glob, watch ) {
  23. const srcDir = path.join( root, glob );
  24. let stream = gulp.src( srcDir );
  25. if ( watch ) {
  26. stream = stream
  27. // Let's use plumber only when watching. In other cases we should fail quickly and loudly.
  28. .pipe( gulpPlumber() )
  29. .pipe( gulpWatch( srcDir ) );
  30. }
  31. return stream;
  32. },
  33. /**
  34. * Saves the files piped into this stream to the `dist/` directory.
  35. *
  36. * @param {String} distDir The `dist/` directory path.
  37. * @param {String} format The format of the distribution (`esnext`, `amd`, or `cjs`).
  38. * @returns {Stream}
  39. */
  40. dist( distDir, format ) {
  41. const destDir = path.join( distDir, format );
  42. return gulp.dest( destDir );
  43. },
  44. /**
  45. * Transpiles files piped into this stream to the given format (`amd` or `cjs`).
  46. *
  47. * @param {String} format
  48. * @returns {Stream}
  49. */
  50. transpile( format ) {
  51. const babelModuleTranspilers = {
  52. amd: 'amd',
  53. cjs: 'commonjs'
  54. };
  55. const babelModuleTranspiler = babelModuleTranspilers[ format ];
  56. if ( !babelModuleTranspiler ) {
  57. throw new Error( `Incorrect format: ${ format }` );
  58. }
  59. return new stream.PassThrough( { objectMode: true } )
  60. // Not used so far, but we'll need it.
  61. // .pipe( utils.pickVersionedFile( format ) )
  62. .pipe( babel( {
  63. plugins: [ `transform-es2015-modules-${ babelModuleTranspiler }` ],
  64. // Ensure that all paths ends with '.js' because Require.JS (unlike Common.JS/System.JS)
  65. // will not add it to module names which look like paths.
  66. resolveModuleSource: ( source ) => {
  67. return utils.appendModuleExtension( source );
  68. }
  69. } ) );
  70. },
  71. /**
  72. * Creates a function adding transpilation pipes to the `pipes` param.
  73. * Used to generate `formats.reduce()` callback where `formats` is an array
  74. * of formats that should be generated.
  75. *
  76. * @param {String} distDir The `dist/` directory path.
  77. * @returns {Function}
  78. */
  79. addFormat( distDir ) {
  80. return ( pipes, format ) => {
  81. const conversionPipes = [];
  82. if ( format != 'esnext' ) {
  83. conversionPipes.push( utils.transpile( format ) );
  84. }
  85. conversionPipes.push(
  86. utils.dist( distDir, format )
  87. .on( 'data', ( file ) => {
  88. gutil.log( `Finished writing '${ gutil.colors.cyan( file.path ) }'` );
  89. } )
  90. );
  91. pipes.push( multipipe.apply( null, conversionPipes ) );
  92. return pipes;
  93. };
  94. },
  95. // Not used so far, but will allow us to pick one of files suffixed with the format (`__esnext`, `__amd`, or `__cjs`).
  96. // This will be needed when we'll want to have different piece of code for specific formats.
  97. //
  98. // For example: we'll have `loader__esnext.js`, `loader__amd.js` and `loader__cjs.js`. After applying this
  99. // transformation when compiling code for a specific format the proper file will be renamed to `loader.js`.
  100. pickVersionedFile( format ) {
  101. return rename( ( path ) => {
  102. const regexp = new RegExp( `__${ format }$` );
  103. path.basename = path.basename.replace( regexp, '' );
  104. } );
  105. },
  106. /**
  107. * Moves files out of `node_modules/ckeditor5-xxx/src/*` directories to `ckeditor5-xxx/*`.
  108. *
  109. * @param {RegExp} modulePathPattern
  110. * @returns {Stream}
  111. */
  112. unpackModules( modulePathPattern ) {
  113. return rename( ( file ) => {
  114. file.dirname = file.dirname.replace( modulePathPattern, `${ sep }$1${ sep }` );
  115. // Remove now empty src/ dirs.
  116. if ( !file.extname && file.basename == 'src' ) {
  117. file.basename = '';
  118. }
  119. } );
  120. },
  121. /**
  122. * Adds `ckeditor5/` to a file path.
  123. *
  124. * @returns {Stream}
  125. */
  126. wrapCKEditor5Module() {
  127. return rename( ( file ) => {
  128. file.dirname = path.join( file.dirname, 'ckeditor5' );
  129. } );
  130. },
  131. /**
  132. * Appends file extension to file URLs. Tries to not touch named modules.
  133. *
  134. * @param {String} source
  135. * @returns {String}
  136. */
  137. appendModuleExtension( source ) {
  138. if ( /^https?:|\.[\/\\]/.test( source ) && !/\.js$/.test( source ) ) {
  139. return source + '.js';
  140. }
  141. return source;
  142. }
  143. };
  144. module.exports = utils;