utils.js 6.8 KB

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