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 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. * @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. * Transforms first letter of passed string to the uppercase.
  58. *
  59. * @param {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. * Renders content for the 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 type 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} Entry file content.
  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. // Returns a list of plugin imports.
  80. function renderPluginImports( features = [] ) {
  81. let templateFragment = '';
  82. for ( let feature of features ) {
  83. feature = utils.getPluginPath( feature );
  84. const featurePath = path.relative( dir, feature );
  85. // Generate unique feature name.
  86. // In case of two ore more features will have the same name:
  87. // features: [
  88. // 'typing',
  89. // 'path/to/other/plugin/typing'
  90. // ]
  91. let featureName = utils.capitalize( path.basename( feature, '.js' ) );
  92. let i = 0;
  93. while ( featureNames.indexOf( featureName ) >= 0 ) {
  94. featureName = utils.capitalize( path.basename( feature, `.js` ) ) + ( ++i ).toString();
  95. }
  96. templateFragment += `import ${ featureName } from '${ featurePath }';\n`;
  97. featureNames.push( featureName );
  98. }
  99. return templateFragment;
  100. }
  101. return `
  102. 'use strict';
  103. // Babel helpers.
  104. import '${ path.relative( dir, 'node_modules/regenerator-runtime/runtime.js' ) }';
  105. import ${ creatorName } from '${ creatorPath }';
  106. ${ renderPluginImports( data.features ) }
  107. export default class ${ data.moduleName } extends ${ creatorName } {
  108. static create( element, config = {} ) {
  109. if ( !config.features ) {
  110. config.features = [];
  111. }
  112. config.features = [ ...config.features, ${ featureNames.join( ', ' ) } ];
  113. return ${ creatorName }.create( element, config );
  114. }
  115. }
  116. `;
  117. },
  118. /**
  119. * Saves files from stream in specific destination and add `.min` suffix to the name.
  120. *
  121. * @param {Stream} stream Source stream.
  122. * @param {String} destination Path to the destination directory.
  123. * @returns {Stream}
  124. */
  125. saveFileFromStreamAsMinified( stream, destination ) {
  126. return stream
  127. .pipe( gulpRename( {
  128. suffix: '.min'
  129. } ) )
  130. .pipe( gulp.dest( destination ) );
  131. },
  132. /**
  133. * Copies specified file to specified destination.
  134. *
  135. * @param {String} from Source path.
  136. * @param {String} to Destination directory.
  137. * @returns {Promise}
  138. */
  139. copyFile( from, to ) {
  140. return new Promise( ( resolve ) => {
  141. gulp.src( from )
  142. .pipe( gulp.dest( to ) )
  143. .on( 'finish', resolve );
  144. } );
  145. },
  146. /**
  147. * Gets size of the file.
  148. *
  149. * @param {String} path Path to the file.
  150. * @returns {Number} Size in bytes.
  151. */
  152. getFileSize( path ) {
  153. return fs.statSync( path ).size;
  154. },
  155. /**
  156. * Gets human readable gzipped size of the file.
  157. *
  158. * @param {String} path Path to the file.
  159. * @returns {Number} Size in bytes.
  160. */
  161. getGzippedFileSize( path ) {
  162. return gzipSize.sync( fs.readFileSync( path ) );
  163. },
  164. /**
  165. * Gets normal and gzipped size of every passed file in specified directory.
  166. *
  167. * @param {Array.<String>} files List of file paths.
  168. * @param {String} [rootDir=''] When each file is in the same directory.
  169. * @returns {Array.<Object>} List with file size data.
  170. *
  171. * Objects contain the following fields:
  172. *
  173. * * name – File name.
  174. * * size – File size in human readable format.
  175. * * gzippedSize – Gzipped file size in human readable format.
  176. */
  177. getFilesSizeStats( files, rootDir = '' ) {
  178. return files.map( ( file ) => {
  179. const filePath = path.join( rootDir, file );
  180. return {
  181. name: path.basename( filePath ),
  182. size: utils.getFileSize( filePath ),
  183. gzippedSize: utils.getGzippedFileSize( filePath )
  184. };
  185. } );
  186. },
  187. /**
  188. * Prints on console summary with a list of files with their size stats.
  189. *
  190. * Title:
  191. * file.js: 1 MB (gzipped: 400 kB)
  192. * file.css 500 kB (gzipped: 100 kB)
  193. *
  194. * @param {String} title Summary title.
  195. * @param {Array.<Object>} filesStats
  196. */
  197. showFilesSummary( title, filesStats ) {
  198. const label = gutil.colors.underline( title );
  199. const filesSummary = filesStats.map( ( file ) => {
  200. return `${ file.name }: ${ prettyBytes( file.size ) } (gzipped: ${ prettyBytes( file.gzippedSize ) })`;
  201. } ).join( '\n' );
  202. gutil.log( gutil.colors.green( `\n${ label }:\n${ filesSummary }` ) );
  203. }
  204. };
  205. // Extend top level utils.
  206. module.exports = Object.assign( utils, mainUtils );