| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- /**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- 'use strict';
- const path = require( 'path' );
- const gulp = require( 'gulp' );
- const rename = require( 'gulp-rename' );
- const replace = require( 'gulp-replace' );
- const gulpBabel = require( 'gulp-babel' );
- const gutil = require( 'gulp-util' );
- const gulpFilter = require( 'gulp-filter' );
- const multipipe = require( 'multipipe' );
- const PassThrough = require( 'stream' ).PassThrough;
- const through = require( 'through2' );
- const fs = require( 'fs' );
- const sass = require( 'node-sass' );
- const minimist = require( 'minimist' );
- const sprite = require( 'gulp-svg-sprite' );
- const pipe = require( 'multipipe' );
- const filter = require( 'gulp-filter' );
- const mainUtils = require( '../utils' );
- const utils = {
- /**
- * Code which can be appended to a transpiled (into AMD) test files in order to
- * load the 'tests' module and defer launching Bender until it's ready.
- *
- * Note: This code will not be transpiled so keep it in ES5.
- */
- benderLauncherCode:
- `
- require( [ 'tests' ], bender.defer(), function( err ) {
- // The problem with Require.JS is that there are no stacktraces if we won't log this.
- console.error( err );
- console.log( err.stack );
- } );
- `,
- /**
- * Module formats supported by the builder.
- */
- SUPPORTED_FORMATS: [ 'esnext', 'amd', 'cjs' ],
- /**
- * Creates a simple duplex stream.
- *
- * @param {Function} [callback] A callback which will be executed with each chunk.
- * @returns {Stream}
- */
- noop( callback ) {
- if ( !callback ) {
- return new PassThrough( { objectMode: true } );
- }
- return through( { objectMode: true }, ( file, encoding, throughCallback ) => {
- callback( file );
- throughCallback( null, file );
- } );
- },
- /**
- * Saves the files piped into this stream to the `build/` directory.
- *
- * @param {String} buildDir The `build/` directory path.
- * @param {String} format The format of the buildribution (`esnext`, `amd`, or `cjs`).
- * @returns {Stream}
- */
- destBuild( buildDir, format ) {
- const destDir = path.join( buildDir, format );
- return gulp.dest( destDir );
- },
- /**
- * Creates a function generating convertion streams.
- * Used to generate `formats.reduce()` callback where `formats` is an array of formats that should be generated.
- *
- * @param {String} buildDir The `build/` directory path.
- * @returns {Function}
- */
- getConversionStreamGenerator( buildDir ) {
- return ( pipes, format ) => {
- const conversionPipes = [];
- conversionPipes.push( utils.pickVersionedFile( format ) );
- if ( format != 'esnext' ) {
- // Convert src files.
- const filterSource = gulpFilter( ( file ) => {
- return utils.isSourceFile( file ) && utils.isJSFile( file );
- }, { restore: true } );
- const transpileSource = utils.transpile( format, utils.getBabelOptionsForSource( format ) );
- conversionPipes.push(
- filterSource,
- transpileSource,
- utils.fixCoverage(),
- filterSource.restore
- );
- // Convert test files.
- const filterTests = gulpFilter( ( file ) => {
- return utils.isTestFile( file ) && utils.isJSFile( file );
- }, { restore: true } );
- const transpileTests = utils.transpile( format, utils.getBabelOptionsForTests( format ) );
- conversionPipes.push(
- filterTests,
- transpileTests,
- format === 'amd' ? utils.appendBenderLauncher() : utils.noop(),
- filterTests.restore
- );
- }
- conversionPipes.push(
- utils.destBuild( buildDir, format ),
- utils.noop( ( file ) => {
- gutil.log( `Finished writing '${ gutil.colors.cyan( file.path ) }'` );
- } )
- );
- pipes.push( multipipe.apply( null, conversionPipes ) );
- return pipes;
- };
- },
- /**
- * Transpiles files piped into this stream to the given format (`amd` or `cjs`).
- *
- * @param {String} format
- * @returns {Stream}
- */
- transpile( format, options ) {
- return gulpBabel( options )
- .on( 'error', ( err ) => {
- gutil.log( gutil.colors.red( `Error (Babel:${ format })` ) );
- gutil.log( gutil.colors.red( err.message ) );
- console.log( '\n' + err.codeFrame + '\n' );
- } );
- },
- /**
- * Adds istanbul ignore to the code created by babel.
- *
- * @returns {Stream}
- */
- fixCoverage() {
- return replace(
- 'return obj && obj.__esModule ? obj :',
- '/* istanbul ignore next */\n\t\t' +
- 'return obj && obj.__esModule ? obj :' );
- },
- /**
- * Returns an object with Babel options for the source code.
- *
- * @param {String} format
- * @returns {Object} options
- */
- getBabelOptionsForSource( format ) {
- return {
- plugins: utils.getBabelPlugins( format ),
- // Ensure that all paths ends with '.js' because Require.JS (unlike Common.JS/System.JS)
- // will not add it to module names which look like paths.
- resolveModuleSource: format == 'cjs' ? utils.resolveModuleSource : utils.appendModuleExtension
- };
- },
- /**
- * Returns an object with Babel options for the test code.
- *
- * @param {String} format
- * @returns {Object} options
- */
- getBabelOptionsForTests( format ) {
- return {
- plugins: utils.getBabelPlugins( format ),
- resolveModuleSource: format == 'cjs' ? utils.resolveModuleSource : utils.appendModuleExtension,
- moduleIds: true,
- moduleId: 'tests'
- };
- },
- /**
- * Returns an array of Babel plugins to use.
- *
- * @param {String} format
- * @returns {Array}
- */
- getBabelPlugins( format ) {
- const babelModuleTranspilers = {
- amd: 'amd',
- cjs: 'commonjs'
- };
- const babelModuleTranspiler = babelModuleTranspilers[ format ];
- if ( !babelModuleTranspiler ) {
- throw new Error( `Incorrect format: ${ format }` );
- }
- return [
- // Note: When plugin is specified by its name, Babel loads it from a context of a
- // currently transpiled file (in our case - e.g. from ckeditor5-core/src/foo.js).
- // Obviously that fails, since we have all the plugins installed only in ckeditor5/
- // and we want to have them only there to avoid installing them dozens of times.
- //
- // Anyway, I haven't found in the docs that you can also pass a plugin instance here,
- // but it works... so let's hope it will.
- require( `babel-plugin-transform-es2015-modules-${ babelModuleTranspiler }` )
- ];
- },
- /**
- * Appends the {@link #benderLauncherCode} at the end of the file.
- *
- * @returns {Stream}
- */
- appendBenderLauncher() {
- return through( { objectMode: true }, ( file, encoding, callback ) => {
- if ( !file.isNull() ) {
- file.contents = new Buffer( file.contents.toString() + utils.benderLauncherCode );
- }
- callback( null, file );
- } );
- },
- /**
- * Allows us to pick one of files suffixed with the format (`__esnext`, `__amd`, or `__cjs`) and removes
- * files with other suffixes from the stream.
- *
- * For example: we have `load__esnext.js`, `load__amd.js` and `load__cjs.js`. After applying this
- * transformation when compiling code for a specific format the proper file will be renamed to `load.js`.
- * Files not matching a specified format will be removed.
- *
- * @param {String} format
- * @returns {Stream}
- */
- pickVersionedFile( format ) {
- const rejectedFormats = utils.SUPPORTED_FORMATS
- .filter( ( item ) => item !== format );
- const pickRegexp = new RegExp( `__${ format }$` );
- const rejectRegexp = new RegExp( `__(${ rejectedFormats.join( '|' ) }).js$` );
- const pick = rename( ( path ) => {
- path.basename = path.basename.replace( pickRegexp, '' );
- } );
- const remove = gulpFilter( ( file ) => !rejectRegexp.test( file.path ) );
- return multipipe( pick, remove );
- },
- /**
- * Processes paths of files inside CKEditor5 packages.
- *
- * * `ckeditor5-xxx/src/foo/bar.js` -> `ckeditor5/xxx/foo/bar.js`
- * * `ckeditor5-xxx/tests/foo/bar.js` -> `tests/xxx/foo/bar.js`
- *
- * @returns {Stream}
- */
- renamePackageFiles() {
- return rename( ( file ) => {
- const dirFrags = file.dirname.split( path.sep );
- // Validate the input for the clear conscious.
- if ( dirFrags[ 0 ].indexOf( 'ckeditor5-' ) !== 0 ) {
- throw new Error( 'Path should start with "ckeditor5-".' );
- }
- dirFrags[ 0 ] = dirFrags[ 0 ].replace( /^ckeditor5-/, '' );
- const firstFrag = dirFrags[ 1 ];
- if ( firstFrag == 'src' ) {
- // Remove 'src/'.
- dirFrags.splice( 1, 1 );
- // Temporary implementation of the UI lib option. See #88.
- if ( dirFrags[ 0 ] == 'ui-default' ) {
- dirFrags[ 0 ] = 'ui';
- }
- // And prepend 'ckeditor5/'.
- dirFrags.unshift( 'ckeditor5' );
- } else if ( firstFrag == 'tests' ) {
- // Remove 'tests/' from the package dir.
- dirFrags.splice( 1, 1 );
- // And prepend 'tests/'.
- dirFrags.unshift( 'tests' );
- } else {
- throw new Error( 'Path should start with "ckeditor5-*/(src|tests|theme)".' );
- }
- file.dirname = path.join.apply( null, dirFrags );
- } );
- },
- /**
- * Processes paths of files inside the main CKEditor5 package.
- *
- * * `src/foo/bar.js` -> `ckeditor5/foo/bar.js`
- * * `tests/foo/bar.js` -> `tests/ckeditor5/foo/bar.js`
- *
- * @returns {Stream}
- */
- renameCKEditor5Files() {
- return rename( ( file ) => {
- const dirFrags = file.dirname.split( path.sep );
- const firstFrag = dirFrags[ 0 ];
- if ( firstFrag == 'src' ) {
- // Replace 'src/' with 'ckeditor5/'.
- // src/path.js -> ckeditor5/path.js
- dirFrags.splice( 0, 1, 'ckeditor5' );
- } else if ( firstFrag == 'tests' ) {
- // Insert 'ckeditor5/' after 'tests/'.
- // tests/foo.js -> tests/ckeditor5/foo.js
- dirFrags.splice( 1, 0, 'ckeditor5' );
- } else {
- throw new Error( 'Path should start with "src" or "tests".' );
- }
- file.dirname = path.join.apply( null, dirFrags );
- } );
- },
- /**
- * Appends file extension to file URLs. Tries to not touch named modules.
- *
- * @param {String} source
- * @returns {String}
- */
- appendModuleExtension( source ) {
- if ( /^https?:|\.[\/\\]/.test( source ) && !/\.js$/.test( source ) ) {
- return source + '.js';
- }
- return source;
- },
- /**
- * Checks whether a file is a test file.
- *
- * @param {Vinyl} file
- * @returns {Boolean}
- */
- isTestFile( file ) {
- // TODO this should be based on bender configuration (config.tests.*.paths).
- if ( !file.relative.startsWith( 'tests' + path.sep ) ) {
- return false;
- }
- const dirFrags = file.relative.split( path.sep );
- return !dirFrags.some( dirFrag => {
- return dirFrag.startsWith( '_' ) && dirFrag != '_utils-tests';
- } );
- },
- /**
- * Checks whether a file is a source file.
- *
- * @param {Vinyl} file
- * @returns {Boolean}
- */
- isSourceFile( file ) {
- return !utils.isTestFile( file );
- },
- /**
- * Checks whether a file is a JS file.
- *
- * @param {Vinyl} file
- * @returns {Boolean}
- */
- isJSFile( file ) {
- return file.path.endsWith( '.js' );
- },
- /**
- * Finds all CKEditor5 package directories in "node_modules" folder.
- *
- * @param {String} rootDir A root directory containing "node_modules" folder.
- * @returns {Array} Array of ckeditor5-* package directory paths.
- */
- getPackages( rootDir ) {
- // Find all CKEditor5 package directories. Resolve symlinks so we watch real directories
- // in order to workaround https://github.com/paulmillr/chokidar/issues/419.
- return fs.readdirSync( path.join( rootDir, 'node_modules' ) )
- // Look for ckeditor5-* directories.
- .filter( fileName => {
- return fileName.indexOf( 'ckeditor5-' ) === 0;
- } )
- // Resolve symlinks and keep only directories.
- .map( fileName => {
- let filePath = path.join( rootDir, 'node_modules', fileName );
- let stat = fs.lstatSync( filePath );
- if ( stat.isSymbolicLink() ) {
- filePath = fs.realpathSync( filePath );
- stat = fs.lstatSync( filePath );
- }
- if ( stat.isDirectory() ) {
- return filePath;
- }
- // Filter...
- return false;
- } )
- // ...those out.
- .filter( filePath => filePath );
- },
- /**
- * Filters theme entry points only from a stream of SCSS files.
- *
- * @returns {Stream}
- */
- filterThemeEntryPoints() {
- return filter( '**/theme.scss' );
- },
- /**
- * Given the input stream of theme entry-point files (theme.scss), this method:
- * 1. Collects paths to entry-point.
- * 2. Builds the output CSS theme file using aggregated entry-points.
- * 3. Returns a stream containing built CSS theme file.
- *
- * @param {String} fileName The name of the output CSS theme file.
- * @returns {Stream}
- */
- compileThemes( fileName ) {
- const paths = [];
- const stream = through.obj( collectThemeEntryPoint, renderThemeFromEntryPoints );
- function collectThemeEntryPoint( file, enc, callback ) {
- paths.push( file.path );
- callback();
- }
- function renderThemeFromEntryPoints( callback ) {
- gutil.log( `Compiling '${ gutil.colors.cyan( fileName ) }' from ${ gutil.colors.cyan( paths.length ) } entry points...` );
- // Sort to make sure theme is the very first SASS to build. Otherwise,
- // packages using mixins and variables from that theme will throw errors
- // because such are not available at this stage of compilation.
- const dataToRender = paths.sort( a => -a.indexOf( 'ckeditor5-theme' ) )
- // Make sure windows\\style\\paths are preserved.
- .map( p => `@import "${ p.replace( /\\/g, '\\\\' ) }";` )
- .join( '\n' );
- try {
- const rendered = sass.renderSync( utils.getSassOptions( dataToRender ) );
- stream.push( new gutil.File( {
- path: fileName,
- contents: new Buffer( rendered.css )
- } ) );
- callback();
- } catch ( err ) {
- callback( err );
- }
- }
- return stream;
- },
- /**
- * Parses command line arguments and returns them as a user-friendly hash.
- *
- * @param {String} dataToRender
- * @returns {Object}
- */
- getSassOptions( dataToRender ) {
- return {
- data: dataToRender,
- sourceMap: true,
- sourceMapEmbed: true,
- outputStyle: 'expanded',
- sourceComments: true
- };
- },
- /**
- * Parses command line arguments and returns them as a user-friendly hash.
- *
- * @returns {Object} options
- * @returns {Array} [options.formats] Array of specified output formats ("esnext" or "amd").
- * @returns {Boolean} [options.watch] A flag which enables watch mode.
- */
- parseArguments() {
- const options = minimist( process.argv.slice( 2 ), {
- string: [
- 'formats'
- ],
- boolean: [
- 'watch'
- ],
- default: {
- formats: 'amd',
- watch: false
- }
- } );
- options.formats = options.formats.split( ',' );
- return options;
- },
- /**
- * Given a stream of .svg files it returns a stream containing JavaScript
- * icon sprite file.
- *
- * @returns {Stream}
- */
- compileIconSprite() {
- return sprite( utils.getIconSpriteOptions() );
- },
- /**
- * Returns svg-sprite util options to generate <symbol>-based, JavaScript
- * sprite file.
- *
- * @returns {Object}
- */
- getIconSpriteOptions() {
- return {
- shape: {
- id: {
- // Note: Consider unix/style/paths and windows\\style\\paths.
- generator: name => `ck-icon-${ name.match( /([^\/\\]*)\.svg$/ )[ 1 ] }`
- },
- },
- svg: {
- xmlDeclaration: false,
- doctypeDeclaration: false,
- },
- mode: {
- symbol: {
- dest: './', // Flatten symbol/ folder.
- inline: true,
- render: {
- js: {
- template: path.join( __dirname, 'iconmanagermodel.tpl' ),
- dest: 'iconmanagermodel.js',
- }
- }
- }
- }
- };
- },
- /**
- * Given a stream of files it returns an array of gulp-mirror streams outputting
- * files to `build/[formats]/theme/` directories for each of desired output formats (cjs, amd, etc.).
- *
- * @param {String} buildDir A path to /build directory.
- * @param {Array} formats An array of desired output formats.
- * @param {Function} [transformationStream] A stream used to transform files before they're saved to
- * desired `build/[formats]/theme` directories. Useful for transpilation.
- * @returns {Stream[]} An array of streams.
- */
- getThemeFormatDestStreams( buildDir, formats, transformationStream ) {
- return formats.map( f => {
- return pipe(
- transformationStream ? transformationStream( f ) : utils.noop(),
- gulp.dest( path.join( buildDir, f, 'theme' ) ),
- utils.noop( file => {
- gutil.log( `Output for ${ gutil.colors.cyan( f ) } is '${ gutil.colors.cyan( file.path ) }'.` );
- } )
- );
- } );
- },
- /**
- * Resolves CommonJS module source path.
- *
- * @param {String} source Module path passed to require() method.
- * @param {String} file Path to a file where require() method is called.
- * @returns {String} Fixed module path.
- */
- resolveModuleSource( source, file ) {
- // If path is relative - leave it as is.
- if ( !path.isAbsolute( source ) ) {
- return source;
- }
- // Find relative path of test file from cwd directory.
- let testFile = path.relative( process.cwd(), file );
- // Append `/` as all files uses it as root inside transpiled versions.
- testFile = path.join( path.sep, testFile );
- // Find relative path from test file to source.
- let relativePath = path.relative( path.dirname( testFile ), path.dirname( source ) );
- relativePath = path.join( relativePath, path.basename( source ) );
- // Convert windows path to posix.
- relativePath = relativePath.replace( /\\/g, '/' );
- return utils.appendModuleExtension( ( relativePath.startsWith( '../' ) ? '' : './' ) + relativePath );
- }
- };
- // Extend top level utils.
- module.exports = Object.assign( utils, mainUtils );
|