utils.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. const fs = require( 'fs' );
  7. const path = require( 'path' );
  8. const gulp = require( 'gulp' );
  9. const gulpRename = require( 'gulp-rename' );
  10. const gutil = require( 'gulp-util' );
  11. const prettyBytes = require( 'pretty-bytes' );
  12. const gzipSize = require( 'gzip-size' );
  13. const mainUtils = require( '../utils' );
  14. const minimist = require( 'minimist' );
  15. const utils = {
  16. /**
  17. * Parses command line arguments and returns them as a user-friendly hash.
  18. *
  19. * @returns {Object} options
  20. * @returns {String} [options.config] Path to the bundle configuration file.
  21. */
  22. parseArguments() {
  23. const options = minimist( process.argv.slice( 2 ), {
  24. string: [
  25. 'config'
  26. ]
  27. } );
  28. return options;
  29. },
  30. /**
  31. * When module path is not relative then treat this path as a path to the one of the ckeditor5 default module
  32. * (relative to ./bundle/exnext/ckedotir5) and add prefix `./build/esnext/ckeditor5/` to this path.
  33. *
  34. * @param {String} modulePath Path the ckeditor5 module.
  35. */
  36. getFullPath( modulePath ) {
  37. // If path is not a relative path (no leading ./ or ../).
  38. if ( modulePath.charAt( 0 ) != '.' ) {
  39. return `./${ path.join( 'build/esnext/ckeditor5', modulePath ) }`;
  40. }
  41. return modulePath;
  42. },
  43. /**
  44. * Resolves a simplified plugin name to a real path if only name is passed.
  45. * E.g. 'delete' will be transformed to './build/esnext/ckeditor5/delete/delete.js'.
  46. *
  47. * @param {String} name
  48. * @returns {String} Path to the module.
  49. */
  50. getPluginPath( name ) {
  51. if ( name.indexOf( '/' ) >= 0 ) {
  52. return utils.getFullPath( name );
  53. }
  54. return utils.getFullPath( `${ name }/${ name }.js` );
  55. },
  56. /**
  57. * Transform first letter of passed string to the upper case.
  58. *
  59. * @params {String} string String that will be transformed.
  60. * @returns {String} Transformed string.
  61. */
  62. capitalize( string ) {
  63. return string.charAt( 0 ).toUpperCase() + string.slice( 1 );
  64. },
  65. /**
  66. * Render content for entry file which needs to be passed as main file to the Rollup.
  67. *
  68. * @param {String} dir Path to the entryfile directory. Import paths need to be relative to this directory.
  69. * @param {Object} data Configuration object.
  70. * @param {String} [data.moduleName] Name of the editor class exposed as global variable by bundle. e.g. MyCKEditor.
  71. * @param {String} [data.editor] Path to the editor version e.g. `classic-editor/classic.js`.
  72. * @param {Array.<String>} [data.features] List of paths or names to features which need to be included in bundle.
  73. * @returns {string}
  74. */
  75. renderEntryFileContent( dir, data ) {
  76. const creatorName = utils.capitalize( path.basename( data.editor, '.js' ) );
  77. const creatorPath = path.relative( dir, utils.getFullPath( data.editor ) );
  78. let featureNames = [];
  79. function renderPluginImports( features = [] ) {
  80. let templateFragment = '';
  81. for ( let feature of features ) {
  82. feature = utils.getPluginPath( feature );
  83. const featurePath = path.relative( dir, feature );
  84. // Generate unique feature name.
  85. // In case of two ore more features will have the same name but different path
  86. // 'typing', 'path/to/other/plugin/typing'
  87. let featureName = utils.capitalize( path.basename( feature, '.js' ) );
  88. let i = 0;
  89. while ( featureNames.indexOf( featureName ) >= 0 ) {
  90. featureName = utils.capitalize( path.basename( feature, `.js` ) ) + ( ++i ).toString();
  91. }
  92. templateFragment += `import ${ featureName } from '${ featurePath }';\n`;
  93. featureNames.push( featureName );
  94. }
  95. return templateFragment;
  96. }
  97. return `
  98. 'use strict';
  99. // Babel helpers.
  100. import '${ path.relative( dir, 'node_modules/regenerator-runtime/runtime.js' ) }';
  101. import ${ creatorName } from '${ creatorPath }';
  102. ${ renderPluginImports( data.features ) }
  103. export default class ${ data.moduleName } extends ${ creatorName } {
  104. static create( element, config = {} ) {
  105. if ( !config.features ) {
  106. config.features = [];
  107. }
  108. config.features = [ ...config.features, ${ featureNames.join( ', ' ) } ];
  109. return ${ creatorName }.create( element, config );
  110. }
  111. }
  112. `;
  113. },
  114. /**
  115. * Save files from stream in specific destination and add `.min` suffix to the name.
  116. *
  117. * @param {Stream} stream
  118. * @param {String} destination path
  119. * @returns {Stream}
  120. */
  121. saveFileFromStreamAsMinified( stream, destination ) {
  122. return stream
  123. .pipe( gulpRename( {
  124. suffix: '.min'
  125. } ) )
  126. .pipe( gulp.dest( destination ) );
  127. },
  128. /**
  129. * Copy specified file to specified destination.
  130. *
  131. * @param {String} from file path
  132. * @param {String} to copied file destination
  133. * @returns {Promise}
  134. */
  135. copyFile( from, to ) {
  136. return new Promise( ( resolve ) => {
  137. gulp.src( from )
  138. .pipe( gulp.dest( to ) )
  139. .on( 'finish', resolve );
  140. } );
  141. },
  142. /**
  143. * Get size of the file.
  144. *
  145. * @param {String} path path to the file
  146. * @returns {Number} size size in bytes
  147. */
  148. getFileSize( path ) {
  149. return fs.statSync( path ).size;
  150. },
  151. /**
  152. * Get human readable gzipped size of the file.
  153. *
  154. * @param {String} path path to the file
  155. * @returns {Number} size size in bytes
  156. */
  157. getGzippedFileSize( path ) {
  158. return gzipSize.sync( fs.readFileSync( path ) );
  159. },
  160. /**
  161. * Get normal and gzipped size of every passed file in specified directory.
  162. *
  163. * @param {Array.<String>} files
  164. * @param {String} [rootDir='']
  165. * @returns {Array<Object>} List with file size data
  166. */
  167. getFilesSizeStats( files, rootDir = '' ) {
  168. return files.map( ( file ) => {
  169. const filePath = path.join( rootDir, file );
  170. return {
  171. name: path.basename( filePath ),
  172. size: utils.getFileSize( filePath ),
  173. gzippedSize: utils.getGzippedFileSize( filePath )
  174. };
  175. } );
  176. },
  177. /**
  178. * Print on console list of files with their size stats.
  179. *
  180. * Title:
  181. * file.js: 1 MB (gzipped: 400 kB)
  182. * file.css 500 kB (gzipped: 100 kB)
  183. *
  184. * @param {String} title
  185. * @param {Array.<Object>} filesStats
  186. */
  187. showFilesSummary( title, filesStats ) {
  188. const label = gutil.colors.underline( title );
  189. const filesSummary = filesStats.map( ( file ) => {
  190. return `${ file.name }: ${ prettyBytes( file.size ) } (gzipped: ${ prettyBytes( file.gzippedSize ) })`;
  191. } ).join( '\n' );
  192. gutil.log( gutil.colors.green( `\n${ label }:\n${ filesSummary }` ) );
  193. }
  194. };
  195. // Assign properties from top level utils.
  196. module.exports = Object.assign( utils, mainUtils );