8
0

utils.js 6.7 KB

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