8
0
Просмотр исходного кода

Merge pull request #49 from ckeditor/t/45

T/45
Piotr Jasiun 10 лет назад
Родитель
Сommit
8c0125b6dd

+ 1 - 0
.gitignore

@@ -10,3 +10,4 @@
 node_modules/**
 build/**
 coverage/**
+dist/**

+ 1 - 5
.jshintrc

@@ -11,9 +11,5 @@
 	"strict": true,
 	"varstmt": true,
 
-	"globalstrict": true,
-
-	"globals": {
-		"CKEDITOR": false
-	}
+	"globalstrict": true
 }

+ 5 - 20
bender.js

@@ -2,9 +2,6 @@
 
 'use strict';
 
-// Set it to true to test with the build version.
-const isBuild = false;
-
 const config = {
 	plugins: [
 		'benderjs-chai',
@@ -21,9 +18,9 @@ const config = {
 		'ckeditor': {
 			path: '.',
 			files: [
-				'node_modules/requirejs/require.js',
-				'ckeditor.js'
-			]
+				'node_modules/requirejs/require.js'
+			],
+			basePath: '/apps/ckeditor/dist/amd/'
 		}
 	},
 
@@ -40,22 +37,10 @@ const config = {
 
 	coverage: {
 		paths: [
-			'ckeditor.js',
-			'src/**/*.js',
-			'node_modules/ckeditor5-*/src/**/*.js',
-			'!node_modules/ckeditor5-*/src/lib/**'
+			'dist/amd/**/*.js',
+			'!dist/amd/ckeditor5-*/lib/**'
 		]
 	}
 };
 
-if ( isBuild ) {
-	// Change the 'ckeditor' application to point to the build.
-	config.applications.ckeditor = {
-		path: 'build',
-		files: [
-			'ckeditor.js'
-		]
-	};
-}
-
 module.exports = config;

+ 2 - 125
ckeditor.js

@@ -3,131 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
-/* global requirejs, define, require */
-
 'use strict';
 
-// This file is shared by the dev and release versions of CKEditor. It bootstraps the API.
-
-( function( root ) {
-	const CKEDITOR = root.CKEDITOR = {
-		/**
-		 * Computes the value of the `basePath` property.
-		 *
-		 * @private
-		 * @method
-		 * @returns {String} A full URL.
-		 */
-		_getBasePath: getBasePath,
-
-		/**
-		 * The list of dependencies of **named** AMD modules created with `CKEDITOR.define`. This is mainly used to
-		 * trace the dependency tree of plugins.
-		 */
-		_dependencies: {},
-
-		/**
-		 * The full URL for the CKEditor installation directory.
-		 *
-		 * It is possible to manually provide the base path by setting a global variable named `CKEDITOR_BASEPATH`. This
-		 * global variable must be set **before** the editor script loading.
-		 *
-		 *		console.log( CKEDITOR.basePath ); // e.g. 'http://www.example.com/ckeditor/'
-		 *
-		 * @property {String}
-		 */
-		basePath: getBasePath(),
-
-		/**
-		 * Whether the app should work in the "debug mode" (aka "verbose mode").
-		 *
-		 * You can use the `CKEDITOR.isDebug` condition in order to wrap code that should be removed in the build version:
-		 *
-		 *		if ( CKEDITOR.isDebug ) {
-		 *			if ( doSomeSuperUnnecessaryDebugChecks() ) {
-		 *				throw new CKEditorError( 'sth-broke: Kaboom!' );
-		 *			}
-		 *		}
-		 *
-		 * See also {@link #isDev}.
-		 *
-		 * @property
-		 */
-		isDebug: true,
-
-		/**
-		 * Defines an AMD module.
-		 *
-		 * See https://github.com/ckeditor/ckeditor5-design/wiki/AMD for more details about our AMD API.
-		 *
-		 * @method
-		 * @member CKEDITOR
-		 */
-		define( name, deps ) {
-			// If this is a named module with dependencies, save this in the dependency list.
-			if ( Array.isArray( deps ) && name && !this._dependencies[ name ] ) {
-				this._dependencies[ name ] = deps;
-			}
-
-			return define.apply( this, arguments );
-		},
-
-		/**
-		 * Retrieves one or more AMD modules.
-		 *
-		 * Note that the CKEditor AMD API does not download modules on demand so be sure to have their relative scripts
-		 * available in the page.
-		 *
-		 * See https://github.com/ckeditor/ckeditor5-design/wiki/AMD for more details about our AMD API.
-		 *
-		 * @method
-		 * @member CKEDITOR
-		 */
-		require: require
-	};
-
-	requirejs.config( {
-		// Modules are generally relative to the core project.
-		baseUrl: CKEDITOR.basePath + 'node_modules/ckeditor5-core/src/',
-
-		// These configurations will make no difference in the build version because the following paths will be
-		// already defined there.
-		paths: {
-			// Hide the core "ckeditor" under a different name.
-			'ckeditor-core': CKEDITOR.basePath + 'node_modules/ckeditor5-core/src/ckeditor',
-
-			// The dev version overrides for the "ckeditor" module. This is empty on release.
-			'ckeditor-dev': CKEDITOR.basePath + 'src/ckeditor-dev'
-		}
-	} );
-
-	// Define a new "ckeditor" module, which overrides the core one with the above and the dev stuff.
-	define( 'ckeditor', [ 'ckeditor-core', 'ckeditor-dev', 'utils' ], ( core, dev, utils ) => {
-		root.CKEDITOR = utils.extend( {}, core, root.CKEDITOR, ( dev || /* istanbul ignore next */ {} ) );
-
-		return root.CKEDITOR;
-	} );
-
-	function getBasePath() {
-		if ( window.CKEDITOR_BASEPATH ) {
-			return window.CKEDITOR_BASEPATH;
-		}
-
-		const scripts = document.getElementsByTagName( 'script' );
-		const basePathSrcPattern = /(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i;
-		let path;
-
-		// Find the first script that src matches ckeditor.js.
-		[].some.call( scripts, ( script ) => {
-			const match = script.src.match( basePathSrcPattern );
-
-			if ( match ) {
-				path = match[ 1 ];
-
-				return true;
-			}
-		} );
+import CKEDITOR from './ckeditor5-core/ckeditor.js';
 
-		return path;
-	}
-} )( window );
+export default CKEDITOR;

+ 11 - 6
dev/bender/plugins/ckeditor5/lib/index.js

@@ -7,7 +7,7 @@
 
 const path = require( 'path' );
 const files = [
-	path.join( __dirname, '../static/extensions.js' ),
+	path.join( __dirname, '../static/amd.js' ),
 	path.join( __dirname, '../static/tools.js' )
 ];
 
@@ -18,12 +18,17 @@ module.exports = {
 		this.plugins.addFiles( files );
 
 		this.on( 'test:created', ( test ) => {
+			const moduleRegExp = /node_modules\/ckeditor5-([^\/]+)/;
 			let name = test.displayName;
-
-			name = name.replace( /node_modules\/ckeditor5-core/, 'core: ' );
-			name = name.replace( /node_modules\/ckeditor5-plugin-([^\/]+)/, 'plugin!$1: ' );
-
-			test.displayName = name;
+			let module = name.match( moduleRegExp );
+
+			if ( module ) {
+				test.tags.unshift( 'module!' + module[ 1 ] );
+				test.displayName = name.replace( moduleRegExp, '$1: ' );
+			} else {
+				test.tags.unshift( 'module!ckeditor5' );
+				test.displayName = 'ckeditor5: ' + test.displayName;
+			}
 		} );
 
 		// Add this plugins' scripts before the includes pagebuilder (which handles bender-include directives), so

+ 157 - 0
dev/bender/plugins/ckeditor5/static/amd.js

@@ -0,0 +1,157 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* jshint node: false, browser: true, globalstrict: true */
+/* globals bender, require, define */
+
+'use strict';
+
+( () => {
+	/**
+	 * AMD tools related to CKEditor.
+	 */
+	bender.amd = {
+		/**
+		 * Generates an absolute path to an AMD version of a CKEditor module. The function takes care of
+		 * generating a base path for that file, taking into account whether a Bender job is being run
+		 * or a simple test.
+		 *
+		 * The name should be given in a simplified features naming convention. See {@link ckeditor5/path#getModulePath}
+		 * for more details.
+		 *
+		 * @param {String} name The name of the module.
+		 * @returns {String} The absolute path to the module.
+		 */
+		getModulePath( name ) {
+			let appBasePath = bender.basePath;
+			let ckeditorBasePath = bender.config.applications.ckeditor.basePath;
+			let moduleBasePath;
+
+			// Reported: https://github.com/benderjs/benderjs/issues/248
+			// Ugh... make some paths cleanup, because we need to combine these fragments and we don't want to
+			// duplicate '/'. BTW. if you want to touch this make sure you haven't broken Bender jobs which
+			// have different bender.basePaths (no trailing '/', unnecessary 'tests/' fragment).
+			moduleBasePath =
+				appBasePath
+					.split( '/' )
+					.filter( nonEmpty )
+					// When running a job we need to drop the last parth of the base path, which is "tests".
+					.slice( 0, -1 )
+					.concat(
+						ckeditorBasePath.split( '/' ).filter( nonEmpty )
+					)
+					.join( '/' );
+
+			// NOTE: This code is duplicated in CKEDITOR.getModulePath() because we're not able to use here
+			// that function. It may be possible to resolve this once we start using ES6 modules and transpilation
+			// also for tests.
+			if ( name != 'ckeditor' ) {
+				// Resolve shortened feature names to `featureName/featureName`.
+				if ( name.indexOf( '/' ) < 0 ) {
+					name = name + '/' + name;
+				}
+
+				// Add the prefix to shortened paths like `core/editor` (will be `ckeditor5-core/editor`).
+				// Don't add the prefix to the main file and files frok ckeditor5/ module.
+				if ( !( /^ckeditor5\//.test( name ) ) ) {
+					name = 'ckeditor5-' + name;
+				}
+			}
+
+			return '/' + moduleBasePath + '/' + name + '.js';
+		},
+
+		/**
+		 * Shorthand for defining an AMD module.
+		 *
+		 * Note that the module and dependency names must be passed in the simplified features naming convention
+		 * (see {@link #getModulePath}).
+		 *
+		 * For simplicity the dependencies passed to the `body` will be unwrapped
+		 * from the ES6 module object (so only the default export will be available). Also the returned value
+		 * will be automatically handled as a default export.
+		 *
+		 * If you need to define a module which has access to other exports or can export more values,
+		 * use the global `define()` function:
+		 *
+		 *		define( bender.amd.getModulePath( name ), [ 'exports', 'foo', ... ].map( bender.amd.getModulePath ), ( FooModule, ... ) {
+		 *			const FooClass = FooModule.default;
+		 *			const FooOtherProp = FooModule.otherProp;
+		 *
+		 *			exports.default = MyClass;
+		 *			exports.otherProp = 1;
+		 *		} );
+		 *
+		 * **Note:** Since this method automatically unwraps modules from the ES6 module object when passing them
+		 * to the `body` function, circular dependencies will not work. If you need them, either use the raw `define()`
+		 * as shown above, or keep all the definitions outside modules and only access the variables from the scope:
+		 *
+		 *		PluginE = createPlugin( 'E' );
+		 *		PluginF = createPlugin( 'F' );
+		 *
+		 *		PluginF.requires = [ PluginE ];
+		 *		PluginE.requires = [ PluginF ];
+		 *
+		 *		bender.amd.define( 'E', [ 'core/plugin', 'F' ], () => {
+		 *			return PluginE;
+		 *		} );
+		 *
+		 *		bender.amd.define( 'F', [ 'core/plugin', 'E' ], () => {
+		 *			return PluginF;
+		 *		} );
+		 *
+		 * @param {String} name Name of the module.
+		 * @param {String[]} deps Dependencies of the module.
+		 * @param {Function} body Module body.
+		 */
+		define( name, deps, body ) {
+			if ( arguments.length == 2 ) {
+				body = deps;
+				deps = [];
+			}
+
+			const depsPaths = deps.map( bender.amd.getModulePath );
+
+			// Use the exports object instead of returning from modules in order to handle circular deps.
+			// http://requirejs.org/docs/api.html#circular
+			depsPaths.unshift( 'exports' );
+
+			define( bender.amd.getModulePath( name ), depsPaths, function( exports ) {
+				const loadedDeps = Array.from( arguments ).slice( 1 ).map( ( module ) => module.default );
+
+				exports.default = body.apply( this, loadedDeps );
+			} );
+		},
+
+		/**
+		 * Gets an object which holds the CKEditor modules guaranteed to be loaded before tests start.
+		 *
+		 * @params {...String} module The name of the module to load.
+		 * @returns {Object} The object that will hold the loaded modules.
+		 */
+		require() {
+			const modules = {};
+			const done = bender.defer();
+
+			const names = Array.from( arguments );
+			const modulePaths = names.map( bender.amd.getModulePath );
+
+			require( modulePaths, function() {
+				for ( let i = 0; i < names.length; i++ ) {
+					modules[ names[ i ] ] = arguments[ i ].default;
+				}
+
+				// Finally give green light for tests to start.
+				done();
+			} );
+
+			return modules;
+		}
+	};
+
+	function nonEmpty( str ) {
+		return !!str.length;
+	}
+} )();

+ 0 - 50
dev/bender/plugins/ckeditor5/static/extensions.js

@@ -1,50 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/* jshint node: false, browser: true, globalstrict: true */
-/* globals bender, CKEDITOR */
-
-'use strict';
-
-( () => {
-	// Make Bender wait to start running tests.
-	const done = bender.defer();
-
-	// Wait for the "ckeditor" module to be ready to start testing.
-	CKEDITOR.require( [ 'ckeditor' ], done );
-
-	/**
-	 * AMD tools related to CKEditor.
-	 */
-	bender.amd = {
-		/**
-		 * Gets an object which holds the CKEditor modules guaranteed to be loaded before tests start.
-		 *
-		 * @params {...String} module The name of the module to load.
-		 * @returns {Object} The object that will hold the loaded modules.
-		 */
-		require() {
-			const modules = {};
-			const done = bender.defer();
-
-			const names = [].slice.call( arguments );
-
-			// To avoid race conditions with required modules, require `ckeditor` first and then others. This guarantees
-			// that `ckeditor` will be loaded before any other module.
-			CKEDITOR.require( [ 'ckeditor' ], function() {
-				CKEDITOR.require( names, function() {
-					for ( let i = 0; i < names.length; i++ ) {
-						modules[ names[ i ] ] = arguments[ i ] ;
-					}
-
-					// Finally give green light for tests to start.
-					done();
-				} );
-			} );
-
-			return modules;
-		}
-	};
-} )();

+ 0 - 17
dev/tasks/build.js

@@ -1,17 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-const Builder = require( './build/builder' );
-
-module.exports = function( grunt ) {
-	grunt.registerTask( 'build', 'Build a release out of the current development code.', function() {
-		const done = this.async();
-		const builder = new Builder();
-
-		builder.build( done );
-	} );
-};

+ 0 - 236
dev/tasks/build/builder.js

@@ -1,236 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-/**
- * A CKEditor 5 release builder.
- *
- * @class Builder
- */
-class Builder {
-	constructor( target ) {
-		/**
-		 * The target directory where to create the build.
-		 *
-		 * **Warning**: if existing, this directory will be deleted before processing.
-		 *
-		 * @property {String}
-		 */
-		this.target = target || 'build';
-
-		/**
-		 * The temporary directory to use for build processing.
-		 *
-		 * **Warning**: if existing, this directory will be deleted before processing.
-		 *
-		 * @property {String}
-		 */
-		this.tmp = 'build-tmp';
-
-		/**
-		 * The list of tasks to be executed by the `build()` method. Each entry is an Array containing the name of the
-		 * method inside `tasks` to execute and the message to show to the end user when executing it.
-		 *
-		 * @property {Array}
-		 */
-		this.taskList = [
-			[ 'cleanUp', 'Cleaning the "' + this.target + '" directory...' ],
-			[ 'copyToTmp', 'Copying source files for manipulation...' ],
-			[ 'removeAmdNamespace', 'AMD cleanup...' ],
-			[ 'optimize', 'Creating the optimized code...' ],
-			[ 'cleanUpTmp', 'Removing the "' + this.tmp + '" directory...' ]
-		];
-	}
-
-	/**
-	 * Builds a CKEditor release based on the current development code.
-	 *
-	 * @param {Function} [callback] Function to be called when build finishes. It receives `false` on error.
-	 */
-	build( callback ) {
-		const that = this;
-		let stepCounter = 0;
-
-		// Before starting, run the initial checkups.
-		if ( !this.checkUp() ) {
-			console.error( 'Build operation aborted.' );
-			callback( false );
-
-			return;
-		}
-
-		console.log( 'Building CKEditor into the "' + this.target + '" directory:' );
-
-		runNext();
-
-		function runNext() {
-			const next = that.taskList.shift();
-
-			if ( next ) {
-				stepCounter++;
-				console.log( stepCounter + '. ' + next[ 1 ] );
-				Builder.tasks[ next[ 0 ] ].call( that, runNext );
-			} else {
-				if ( callback ) {
-					callback();
-				}
-			}
-		}
-	}
-
-	checkUp() {
-		const fs = require( 'fs' );
-
-		// Stop if the tmp folder already exists.
-		if ( fs.existsSync( this.tmp ) ) {
-			console.error( 'The "' + this.tmp + '" directory already exists. Delete it and try again.' );
-
-			return false;
-		}
-
-		return true;
-	}
-}
-
-/**
- * Holds individual methods for each task executed by the builder.
- *
- * All methods here MUST be called in the builder context by using
- * `builder.tasks.someMethod.call( builder, callback )`.
- */
-Builder.tasks = {
-	/**
-	 * Deletes the `target` and `tmp` directories.
-	 *
-	 * @param {Function} callback Function to be called when the task is done.
-	 * @returns {Object} The callback returned value.
-	 */
-	cleanUp( callback ) {
-		const del = require( 'del' );
-		del.sync( this.target );
-		del.sync( this.tmp );
-
-		return callback();
-	},
-
-	/**
-	 * Copy the local source code of CKEditor and its dependencies to the `tmp` directory for processing.
-	 *
-	 * @param {Function} callback Function to be called when the task is done.
-	 * @returns {Object} The callback returned value.
-	 */
-	copyToTmp( callback ) {
-		const ncp = require( 'ncp' ).ncp;
-		const path = require( 'path' );
-		const fs = require( 'fs' );
-		const tmp = this.tmp;
-
-		const deps = require( '../../../package.json' ).dependencies;
-
-		const toCopy = Object.keys( deps ).filter( function( name ) {
-			return name.indexOf( 'ckeditor5-' ) === 0;
-		} );
-
-		fs.mkdirSync( tmp );
-
-		function copy() {
-			const module = toCopy.shift();
-
-			if ( !module ) {
-				return callback();
-			}
-
-			const dest = path.join( tmp + '/', module );
-
-			if ( !fs.existsSync( dest ) ) {
-				fs.mkdirSync( dest );
-			}
-
-			// Copy the "src" directory only.
-			ncp( path.join( 'node_modules', module, 'src' ), path.join( dest, 'src' ), {
-				dereference: true
-			}, function( err ) {
-				if ( err ) {
-					throw err;
-				}
-
-				copy();
-			} );
-		}
-
-		copy();
-	},
-
-	/**
-	 * Removes the `CKEDITOR` namespace from AMD calls in the `tmp` copy of the source code.
-	 *
-	 * @param {Function} callback Function to be called when the task is done.
-	 * @returns {Object} The callback returned value.
-	 */
-	removeAmdNamespace( callback ) {
-		const replace = require( 'replace' );
-
-		replace( {
-			regex: /^\s*CKEDITOR\.(define|require)/mg,
-			replacement: '$1',
-			paths: [ this.tmp ],
-			recursive: true,
-			silent: true
-		} );
-
-		callback();
-	},
-
-	/**
-	 * Creates the optimized release version of `ckeditor.js` in the `target` directory out of the `tmp` copy of the
-	 * source code.
-	 *
-	 * @param {Function} callback Function to be called when the task is done.
-	 * @returns {Object} The callback returned value.
-	 */
-	optimize( callback ) {
-		const requirejs = require( 'requirejs' );
-
-		const config = {
-			out: this.target + '/ckeditor.js',
-
-			baseUrl: this.tmp + '/ckeditor5-core/src/',
-			paths: {
-				'ckeditor': '../../../ckeditor',
-				'ckeditor-dev': '../../../src/ckeditor-dev',
-				'ckeditor-core': 'ckeditor'
-			},
-
-			include: [ 'ckeditor' ],
-			stubModules: [ 'ckeditor-dev' ],
-
-			//			optimize: 'none',
-			optimize: 'uglify2',
-			preserveLicenseComments: false,
-			wrap: {
-				startFile: [ 'src/build/start.frag', require.resolve( 'almond' ) ],
-				endFile: 'src/build/end.frag'
-			}
-		};
-
-		requirejs.optimize( config, callback );
-	},
-
-	/**
-	 * Deletes `tmp` directory.
-	 *
-	 * @param {Function} callback Function to be called when the task is done.
-	 * @returns {Object} The callback returned value.
-	 */
-	cleanUpTmp( callback ) {
-		const del = require( 'del' );
-		del.sync( this.tmp );
-
-		return callback();
-	}
-};
-
-module.exports = Builder;

+ 109 - 0
dev/tasks/gulp/build.js

@@ -0,0 +1,109 @@
+/* jshint node: true, esnext: true */
+
+'use strict';
+
+const KNOWN_OPTIONS = {
+	build: {
+		string: [
+			'formats'
+		],
+
+		boolean: [
+			'watch'
+		],
+
+		default: {
+			formats: 'amd',
+			watch: false
+		}
+	}
+};
+
+const path = require( 'path' );
+const gulp = require( 'gulp' );
+const del = require( 'del' );
+const merge = require( 'merge-stream' );
+const gulpMirror = require( 'gulp-mirror' );
+const gutil = require( 'gulp-util' );
+const minimist = require( 'minimist' );
+const utils = require( './utils' );
+
+const sep = path.sep;
+const options = minimist( process.argv.slice( 2 ), KNOWN_OPTIONS[ process.argv[ 2 ] ] );
+
+module.exports = ( config ) => {
+	const distDir = path.join( config.ROOT_DIR, config.DIST_DIR );
+
+	const tasks = {
+		/**
+		 * Removes the dist directory.
+		 */
+		clean() {
+			return del( distDir );
+		},
+
+		src: {
+			/**
+			 * Returns a stream of all source files.
+			 *
+			 * @param {Boolean} [watch] Whether the files should be watched.
+			 * @returns {Stream}
+			 */
+			all( watch ) {
+				return merge( tasks.src.main( watch ), tasks.src.ckeditor5( watch ), tasks.src.modules( watch ) );
+			},
+
+			/**
+			 * Returns a stream with just the main file (`ckeditor5/ckeditor.js`).
+			 *
+			 * @param {Boolean} [watch] Whether the files should be watched.
+			 * @returns {Stream}
+			 */
+			main( watch ) {
+				return utils.src( config.ROOT_DIR, 'ckeditor.js', watch );
+			},
+
+			/**
+			 * Returns a stream of all source files from CKEditor 5.
+			 *
+			 * @param {Boolean} [watch] Whether the files should be watched.
+			 * @returns {Stream}
+			 */
+			ckeditor5( watch ) {
+				return utils.src( config.ROOT_DIR, 'src/**/*.js', watch )
+					.pipe( utils.wrapCKEditor5Module() );
+			},
+
+			/**
+			 * Returns a stream of all source files from CKEditor 5 dependencies.
+			 *
+			 * @param {Boolean} [watch] Whether the files should be watched.
+			 * @returns {Stream}
+			 */
+			modules( watch ) {
+				// For an odd reason file.dirname does not contain `node_modules/`. Maybe the base dir
+				// is automatically set to only the varying piece of the path.
+				const modulePathPattern = new RegExp( `(ckeditor5-[^${ sep }]+)${ sep }src` );
+
+				return utils.src( config.ROOT_DIR, 'node_modules/ckeditor5-*/src/**/*.js', watch )
+					.pipe( utils.unpackModules( modulePathPattern ) );
+			}
+		}
+	};
+
+	gulp.task( 'build:clean', tasks.clean );
+
+	gulp.task( 'build', [ 'build:clean' ], () => {
+		const formats = options.formats.split( ',' );
+		const codeStream = tasks.src.all( options.watch )
+			.on( 'data', ( file ) => {
+				gutil.log( `Processing '${ gutil.colors.cyan( file.path ) }'...` );
+			} );
+		const formatPipes = formats.reduce( utils.addFormat( distDir ), [] );
+
+		return codeStream
+			.pipe( gulpMirror.apply( null, formatPipes ) );
+	} );
+
+	return tasks;
+};

+ 170 - 0
dev/tasks/gulp/utils.js

@@ -0,0 +1,170 @@
+/* jshint node: true, esnext: true */
+
+'use strict';
+
+const path = require( 'path' );
+const gulp = require( 'gulp' );
+const rename = require( 'gulp-rename' );
+const babel = require( 'gulp-babel' );
+const gulpWatch = require( 'gulp-watch' );
+const gulpPlumber = require( 'gulp-plumber' );
+const gutil = require( 'gulp-util' );
+const multipipe = require( 'multipipe' );
+
+const sep = path.sep;
+
+const utils = {
+	/**
+	 * Returns a stream of files matching the given glob pattern.
+	 *
+	 * @param {String} root The root directory.
+	 * @param {String} glob The glob pattern.
+	 * @param {Boolean} [watch] Whether to watch the files.
+	 * @returns {Stream}
+	 */
+	src( root, glob, watch ) {
+		const srcDir = path.join( root, glob );
+		let stream = gulp.src( srcDir );
+
+		if ( watch ) {
+			stream = stream
+				// Let's use plumber only when watching. In other cases we should fail quickly and loudly.
+				.pipe( gulpPlumber() )
+				.pipe( gulpWatch( srcDir ) );
+		}
+
+		return stream;
+	},
+
+	/**
+	 * Saves the files piped into this stream to the `dist/` directory.
+	 *
+	 * @param {String} distDir The `dist/` directory path.
+	 * @param {String} format The format of the distribution (`esnext`, `amd`, or `cjs`).
+	 * @returns {Stream}
+	 */
+	dist( distDir, format ) {
+		const destDir = path.join( distDir, format );
+
+		return gulp.dest( destDir );
+	},
+
+	/**
+	 * Transpiles files piped into this stream to the given format (`amd` or `cjs`).
+	 *
+	 * @param {String} format
+	 * @returns {Stream}
+	 */
+	transpile( format ) {
+		const babelModuleTranspilers = {
+			amd: 'amd',
+			cjs: 'commonjs'
+		};
+		const babelModuleTranspiler = babelModuleTranspilers[ format ];
+
+		if ( !babelModuleTranspiler ) {
+			throw new Error( `Incorrect format: ${ format }` );
+		}
+
+		return babel( {
+			plugins: [ `transform-es2015-modules-${ babelModuleTranspiler }` ],
+			// 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: ( source ) => {
+				return utils.appendModuleExtension( source );
+			}
+		} );
+	},
+
+	/**
+	 * Creates a function adding transpilation pipes to the `pipes` param.
+	 * Used to generate `formats.reduce()` callback where `formats` is an array
+	 * of formats that should be generated.
+	 *
+	 * @param {String} distDir The `dist/` directory path.
+	 * @returns {Function}
+	 */
+	addFormat( distDir ) {
+		return ( pipes, format ) => {
+			const conversionPipes = [];
+
+			conversionPipes.push( utils.pickVersionedFile( format ) );
+
+			if ( format != 'esnext' ) {
+				conversionPipes.push( utils.transpile( format ) );
+			}
+
+			conversionPipes.push(
+				utils.dist( distDir, format )
+					.on( 'data', ( file ) => {
+						gutil.log( `Finished writing '${ gutil.colors.cyan( file.path ) }'` );
+					} )
+			);
+
+			pipes.push( multipipe.apply( null, conversionPipes ) );
+
+			return pipes;
+		};
+	},
+
+	/**
+	 * Allows us to pick one of files suffixed with the format (`__esnext`, `__amd`, or `__cjs`).
+	 *
+	 * 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`.
+	 *
+	 * @param {String} format
+	 * @returns {Stream}
+	 */
+	pickVersionedFile( format ) {
+		return rename( ( path ) => {
+			const regexp = new RegExp( `__${ format }$` );
+
+			path.basename = path.basename.replace( regexp, '' );
+		} );
+	},
+
+	/**
+	 * Moves files out of `node_modules/ckeditor5-xxx/src/*` directories to `ckeditor5-xxx/*`.
+	 *
+	 * @param {RegExp} modulePathPattern
+	 * @returns {Stream}
+	 */
+	unpackModules( modulePathPattern ) {
+		return rename( ( file ) => {
+			file.dirname = file.dirname.replace( modulePathPattern, `${ sep }$1${ sep }` );
+
+			// Remove now empty src/ dirs.
+			if ( !file.extname && file.basename == 'src' ) {
+				file.basename = '';
+			}
+		} );
+	},
+
+	/**
+	 * Adds `ckeditor5/` to a file path.
+	 *
+	 * @returns {Stream}
+	 */
+	wrapCKEditor5Module() {
+		return rename( ( file ) => {
+			file.dirname = path.join( file.dirname, 'ckeditor5' );
+		} );
+	},
+
+	/**
+	 * 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;
+	}
+};
+
+module.exports = utils;

+ 14 - 0
gulpfile.js

@@ -0,0 +1,14 @@
+/* jshint node: true, esnext: true */
+
+'use strict';
+
+const gulp = require( 'gulp' );
+
+const config = {
+	ROOT_DIR: '.',
+	DIST_DIR: 'dist'
+};
+
+require( './dev/tasks/gulp/build' )( config );
+
+gulp.task( 'default', [ 'build' ] );

+ 15 - 3
package.json

@@ -9,11 +9,12 @@
   ],
   "dependencies": {
     "ckeditor5-core": "ckeditor/ckeditor5-core",
-    "ckeditor5-plugin-devtest": "ckeditor/ckeditor5-plugin-devtest",
-    "requirejs": "~2.1"
+    "requirejs": "Reinmar/requirejs"
   },
   "devDependencies": {
     "almond": "^0.3.0",
+    "babel-plugin-transform-es2015-modules-amd": "^6.1.18",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.2.0",
     "benderjs": "^0.4.1",
     "benderjs-chai": "^0.2.0",
     "benderjs-coverage": "^0.2.1",
@@ -27,9 +28,20 @@
     "grunt-githooks": "^0",
     "grunt-jscs": "^2.0.0",
     "grunt-text-replace": "^0.4.0",
+    "gulp": "^3.9.0",
+    "gulp-babel": "^6.1.0",
+    "gulp-mirror": "^0.4.0",
+    "gulp-plumber": "^1.0.1",
+    "gulp-rename": "^1.2.2",
+    "gulp-sourcemaps": "^1.6.0",
+    "gulp-util": "^3.0.7",
+    "gulp-watch": "^4.3.5",
     "inquirer": "^0.11.0",
     "istanbul": "^0.4.1",
+    "merge-stream": "^1.0.0",
+    "minimist": "^1.2.0",
     "mocha": "^2.2.5",
+    "multipipe": "^0.2.1",
     "ncp": "^2.0.0",
     "replace": "^0.3.0",
     "shelljs": "^0",
@@ -47,4 +59,4 @@
     "test": "mocha dev/tests",
     "coverage": "istanbul cover _mocha dev/tests/"
   }
-}
+}

+ 0 - 18
src/build/end.frag

@@ -1,18 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-// This is the code that will go after the compiled source code in the ckeditor.js build.
-//
-// The following are the parts that compose the build:
-//
-//  * start.frag
-//  * Almond.js source code
-//  * CKEditor source code
-//  * end.frag
-
-/************************ end.frag START */
-
-	return require( 'ckeditor' );
-} );

+ 0 - 28
src/build/start.frag

@@ -1,28 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-// This is the code that will precede the compiled source code in the ckeditor.js build.
-//
-// The following are the parts that compose the build:
-//
-//  * start.frag
-//  * Almond.js source code
-//  * CKEditor source code
-//  * end.frag
-
-( function( root, factory ) {
-	// Register the CKEDITOR global.
-	root.CKEDITOR = factory();
-
-	// Make the build an AMD module.
-	// https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property-
-	if ( typeof define == 'function' && define.amd ) {
-		define( function() {
-			return root.CKEDITOR;
-		} );
-	}
-} )( this, function() {
-
-/************************ start.frag END */

+ 0 - 40
src/ckeditor-dev.js

@@ -1,40 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/* global define, requirejs, CKEDITOR */
-
-'use strict';
-
-define( 'ckeditor-dev', () => {
-	return {
-		/**
-		 * A flag specifying whether CKEditor is running in development mode (original source code).
-		 *
-		 * This property is not defined in production (compiled, build code).
-		 *
-		 * See also {@link #isDebug}.
-		 *
-		 * @member CKEDITOR
-		 */
-		isDev: true,
-
-		// Documented in ckeditor5-core/ckeditor.
-		getPluginPath( name ) {
-			return this.basePath + 'node_modules/ckeditor-plugin-' + name + '/src/';
-		}
-	};
-} );
-
-// For the dev version, we override the "plugin" module.
-requirejs.config( {
-	paths: {
-		// The RequireJS "plugin" plugin.
-		'plugin': CKEDITOR.basePath + 'src/plugin',
-
-		// Due to name conflict with the above, we have to save a reference to the core "plugin" module.
-		// See src/plugin.js for more details.
-		'plugin-core': CKEDITOR.basePath + 'node_modules/ckeditor5-core/src/plugin'
-	}
-} );

+ 37 - 0
src/load__amd.js

@@ -0,0 +1,37 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+// We import the 'require' module, so Require.JS gives us a localized version of require().
+// Otherwise we would use the global one which resolves paths relatively to the base dir.
+import require from 'require';
+
+/**
+ * Loads a module.
+ *
+ *		load( CKEDITOR.getModulePath( 'core/editor' ) )
+ *			.then( ( EditorModule ) => {
+ *				const Editor = EditorModule.default;
+ *			} );
+ *
+ * @param {String} modulePath Path to the module, relative to `ckeditor.js`'s parent directory (the root).
+ * @returns {Promise}
+ */
+export default function load( modulePath ) {
+	modulePath = '../' + modulePath;
+
+	return new Promise( ( resolve, reject ) => {
+		require(
+			[ modulePath ],
+			( module ) => {
+				resolve( module );
+			},
+			( err ) => {
+				reject( err );
+			}
+		);
+	} );
+}

+ 16 - 0
src/load__cjs.js

@@ -0,0 +1,16 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/* global require */
+
+export default function load( modulePath ) {
+	modulePath = '../' + modulePath;
+
+	return new Promise( ( resolve ) => {
+		resolve( require( modulePath ) );
+	} );
+}

+ 18 - 0
src/load__esnext.js

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/* global System */
+
+export default function load( modulePath ) {
+	modulePath = '../' + modulePath;
+
+	return System
+		.import( modulePath )
+		.then( ( module ) => {
+			return module;
+		} );
+}

+ 90 - 0
src/path.js

@@ -0,0 +1,90 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const path = {
+	/**
+	 * The full URL for the CKEditor installation directory.
+	 *
+	 * It is possible to manually provide the base path by setting a global variable named `CKEDITOR_BASEPATH`. This
+	 * global variable must be set **before** the editor script loading.
+	 *
+	 *		console.log( CKEDITOR.basePath ); // e.g. 'http://www.example.com/ckeditor/'
+	 *
+	 * @readonly
+	 * @property {String}
+	 */
+	basePath: getBasePath(),
+
+	/**
+	 * Resolves a simplified module name convention to a real path. The returned
+	 * paths are relative to the main `ckeditor.js` file, but they do not start with `./`.
+	 *
+	 * For instance:
+	 *
+	 * * `foo` will be transformed to `ckeditor5-foo/foo.js`,
+	 * * `ckeditor` to `ckeditor.js`,
+	 * * `core/editor` to `ckeditor5-core/editor.js` and
+	 * * `foo/bar/bom` to `ckeditor5-foo/bar/bom.js`.
+	 *
+	 * @param {String} name
+	 * @returns {String} Path to the module.
+	 */
+	getModulePath( name ) {
+		//
+		// Note: This piece of code is duplicated in bender.amd.getModulePath().
+		//
+
+		if ( name != 'ckeditor' ) {
+			// Resolve shortened feature names to `featureName/featureName`.
+			if ( name.indexOf( '/' ) < 0 ) {
+				name = name + '/' + name;
+			}
+
+			// Add the prefix to shortened paths like `core/editor` (will be `ckeditor5-core/editor`).
+			// Don't add the prefix to the main file and files frok ckeditor5/ module.
+			if ( !( /^ckeditor5\//.test( name ) ) ) {
+				name = 'ckeditor5-' + name;
+			}
+		}
+
+		return name + '.js';
+	},
+
+	/**
+	 * Computes the value of the `basePath` property.
+	 *
+	 * @private
+	 * @method
+	 * @returns {String} A full URL.
+	 */
+	_getBasePath: getBasePath
+};
+
+function getBasePath() {
+	if ( window.CKEDITOR_BASEPATH ) {
+		return window.CKEDITOR_BASEPATH;
+	}
+
+	const scripts = document.getElementsByTagName( 'script' );
+	const basePathSrcPattern = /(^|.*[\\\/])ckeditor\.js(?:\?.*|;.*)?$/i;
+	let path;
+
+	// Find the first script that src matches ckeditor.js.
+	Array.from( scripts ).some( ( script ) => {
+		const match = script.src.match( basePathSrcPattern );
+
+		if ( match ) {
+			path = match[ 1 ];
+
+			return true;
+		}
+	} );
+
+	return path;
+}
+
+export default path;

+ 0 - 40
src/plugin.js

@@ -1,40 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/* global define */
-
-'use strict';
-
-// Plugin for RequireJS to properly load CKEditor plugins through the "plugin!name" scheme:
-// "plugin!name" => "node_modules/ckeditor5-plugin-name/name"
-//
-// Due to name conflict with the "ckeditor5-core/plugin" module, a workaround was needed. In this case, we extend the
-// core Plugin class with the necessary RequireJS plugin methods. This should have no harm on the use of the Plugin
-// class.
-
-define( 'plugin', [ 'plugin-core' ], ( CorePlugin ) => {
-	// Called when a "plugin!" module is to be loaded.
-	// http://requirejs.org/docs/plugins.html#apiload
-	CorePlugin.load = function( name, require, onload ) {
-		// We may have a path to plugin modules (e.g. test/somemodule). Here we break the path on slashes.
-		let path = name.split( '/' );
-
-		// Inject the /src/ part right after the plugin name (e.g test/src).
-		path.splice( 1, 0, 'src' );
-
-		// If we didn't have any subpart in the path, inject the plugin name at the end (e.g. test/src/test).
-		if ( path.length == 2 ) {
-			path.push( path[ 0 ] );
-		}
-
-		// Finally point to the right place, relatively to the `ckeditor5-core/src` directory (in node_modules).
-		path = '../../ckeditor5-plugin-' + path.join( '/' );
-
-		// Now require the module again, using the fully resolved path.
-		require( [ path ], onload, onload.error );
-	};
-
-	return CorePlugin;
-} );

+ 0 - 2
tests/.jshintrc

@@ -15,7 +15,6 @@
 	"globalstrict": true,
 
 	"globals": {
-		"CKEDITOR": false,
 		"bender": false,
 		"describe": false,
 		"it": false,
@@ -24,7 +23,6 @@
 		"after": false,
 		"afterEach": false,
 		"expect": false,
-		"bender": false,
 		"sinon": false
 	}
 }

+ 0 - 28
tests/amd/amd.js

@@ -1,28 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-CKEDITOR.define( 'testModule', [ 'ckeditor' ], ( ckeditor ) => {
-	return {
-		test: ( ckeditor && ( typeof ckeditor == 'object' ) )
-	};
-} );
-
-describe( 'CKEDITOR.require()', () => {
-	it( 'should work with a defined module', ( done ) => {
-		CKEDITOR.require( [ 'testModule' ], ( testModule ) => {
-			expect( testModule ).to.have.property( 'test' ).to.be.true();
-			done();
-		} );
-	} );
-
-	it( 'should work with a core module', ( done ) => {
-		CKEDITOR.require( [ 'utils' ], ( utils ) => {
-			expect( utils ).to.be.an( 'object' );
-			done();
-		} );
-	} );
-} );

+ 135 - 0
tests/bender/amd.js

@@ -0,0 +1,135 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global require */
+
+'use strict';
+
+bender.tools.createSinonSandbox();
+
+describe( 'bender.amd', () => {
+	const getModulePath = bender.amd.getModulePath;
+
+	describe( 'getModulePath()', () => {
+		// Thanks to this we'll check whether all paths are relative to ckeditor.js path.
+		const basePath = getModulePath( 'ckeditor' ).replace( /\/ckeditor\.js$/, '/' );
+
+		it( 'generates path for the main file', () => {
+			const path = getModulePath( 'ckeditor' );
+
+			expect( path ).to.match( /\/ckeditor.js$/, 'ends with /ckeditor.js' );
+			expect( path ).to.match( /^\//, 'is absolute' );
+		} );
+
+		it( 'generates path for modules within ckeditor5 package', () => {
+			const path = getModulePath( 'ckeditor5/foo' );
+
+			expect( path ).to.equal( basePath + 'ckeditor5/foo.js' );
+		} );
+
+		it( 'generates path for modules within the core package', () => {
+			const path = getModulePath( 'core/ui/controller' );
+
+			expect( path ).to.equal( basePath + 'ckeditor5-core/ui/controller.js' );
+		} );
+
+		it( 'generates path for modules within some package', () => {
+			const path = getModulePath( 'some/ba' );
+
+			expect( path ).to.equal( basePath + 'ckeditor5-some/ba.js' );
+		} );
+
+		it( 'generates path from simplified feature name', () => {
+			const path = getModulePath( 'foo' );
+
+			expect( path ).to.equal( basePath + 'ckeditor5-foo/foo.js' );
+		} );
+	} );
+
+	describe( 'define()', () => {
+		it( 'defines a module by using global define()', () => {
+			const spy = bender.sinon.spy( window, 'define' );
+			const expectedDeps = [ 'exports' ].concat( [ 'bar', 'ckeditor' ].map( getModulePath ) );
+
+			bender.amd.define( 'test1', [ 'bar', 'ckeditor' ], () => {} );
+
+			expect( spy.calledOnce ).to.be.true;
+			expect( spy.args[ 0 ][ 0 ] ).to.equal( getModulePath( 'test1' ) );
+			expect( spy.args[ 0 ][ 1 ] ).to.deep.equal( expectedDeps );
+		} );
+
+		it( 'maps body args and returned value', () => {
+			const spy = bender.sinon.spy( window, 'define' );
+			const bodySpy = sinon.spy( () => 'ret' );
+
+			bender.amd.define( 'test2', [ 'bar', 'ckeditor' ], bodySpy );
+
+			const realBody = spy.args[ 0 ][ 2 ];
+			const exportsObj = {};
+
+			expect( realBody ).to.be.a( 'function' );
+
+			realBody( exportsObj, { default: 'arg' } );
+
+			expect( exportsObj ).to.have.property( 'default', 'ret', 'it wraps the ret value with an ES6 module obj' );
+			expect( bodySpy.calledOnce ).to.be.true;
+			expect( bodySpy.args[ 0 ][ 0 ] ).to.equal( 'arg', 'it unwraps the args' );
+		} );
+
+		it( 'works with module name and body', () => {
+			const spy = bender.sinon.spy( window, 'define' );
+
+			bender.amd.define( 'test1', () => {} );
+
+			expect( spy.calledOnce ).to.be.true;
+			expect( spy.args[ 0 ][ 0 ] ).to.equal( getModulePath( 'test1' ) );
+			expect( spy.args[ 0 ][ 1 ] ).to.deep.equal( [ 'exports' ] );
+			expect( spy.args[ 0 ][ 2 ] ).to.be.a( 'function' );
+		} );
+
+		// Note: this test only checks whether Require.JS doesn't totally fail when creating a circular dependency.
+		// The value of dependencies are not available anyway inside the bender.amd.define() callbacks because
+		// we lose the late-binding by immediately mapping modules to their default exports.
+		it( 'works with circular dependencies', ( done ) => {
+			bender.amd.define( 'test-circular-a', [ 'test-circular-b' ], () => {
+				return 'a';
+			} );
+
+			bender.amd.define( 'test-circular-b', [ 'test-circular-a' ], () => {
+				return 'b';
+			} );
+
+			require( [ 'test-circular-a', 'test-circular-b' ].map( bender.amd.getModulePath ), ( a, b ) => {
+				expect( a ).to.have.property( 'default', 'a' );
+				expect( b ).to.have.property( 'default', 'b' );
+
+				done();
+			} );
+		} );
+	} );
+
+	describe( 'require', () => {
+		it( 'blocks Bender and loads modules through global require()', () => {
+			let requireCb;
+			const deferCbSpy = sinon.spy();
+
+			bender.sinon.stub( bender, 'defer', () => deferCbSpy );
+			bender.sinon.stub( window, 'require', ( deps, cb ) => {
+				requireCb = cb;
+			} );
+
+			const modules = bender.amd.require( 'foo', 'bar' );
+
+			expect( deferCbSpy.called ).to.be.false;
+
+			requireCb( { default: 1 }, { default: 2 } );
+
+			expect( deferCbSpy.calledOnce ).to.be.true;
+
+			expect( modules ).to.have.property( 'foo', 1 );
+			expect( modules ).to.have.property( 'bar', 2 );
+		} );
+	} );
+} );

+ 19 - 0
tests/ckeditor.js

@@ -0,0 +1,19 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const modules = bender.amd.require( 'ckeditor' );
+let CKEDITOR;
+
+before( () => {
+	CKEDITOR = modules.ckeditor;
+} );
+
+describe( 'CKEDITOR', () => {
+	it( 'is an object', () => {
+		expect( CKEDITOR ).to.be.an( 'object' );
+	} );
+} );

+ 0 - 50
tests/ckeditor/ckeditor.js

@@ -1,50 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-const modules = bender.amd.require( 'ckeditor', 'ckeditor-core' );
-
-describe( 'getPluginPath()', () => {
-	it( 'should return a proper path', () => {
-		const CKEDITOR = modules.ckeditor;
-
-		const basePath = CKEDITOR.basePath;
-		const path = CKEDITOR.getPluginPath( 'test' );
-
-		if ( CKEDITOR.isDev ) {
-			expect( path ).to.equal( basePath + 'node_modules/ckeditor-plugin-test/src/' );
-		} else {
-			expect( path ).to.equal( basePath + 'plugins/test/' );
-		}
-	} );
-
-	it( '(the production version) should work even when in dev', () => {
-		const CKEDITOR = modules.ckeditor;
-		const core = modules[ 'ckeditor-core' ];
-
-		// To be able to run this test on both dev and production code, we need to override getPluginPath with the
-		// core version of it and restore it after testing.
-		const originalGetPluginPath = CKEDITOR.getPluginPath;
-		CKEDITOR.getPluginPath = core.getPluginPath;
-
-		// This test is good for both the development and production codes.
-		const basePath = CKEDITOR.basePath;
-		const path = CKEDITOR.getPluginPath( 'test' );
-
-		// Revert the override before assertions or it will not do it in case of errors.
-		CKEDITOR.getPluginPath = originalGetPluginPath;
-
-		expect( path ).to.equal( basePath + 'plugins/test/' );
-	} );
-} );
-
-describe( 'isDebug', () => {
-	it( 'is a boolean', () => {
-		const CKEDITOR = modules.ckeditor;
-
-		expect( CKEDITOR.isDebug ).to.be.a( 'boolean' );
-	} );
-} );

+ 31 - 0
tests/load.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const modules = bender.amd.require( 'ckeditor5/path', 'ckeditor5/load' );
+let load, path;
+
+before( () => {
+	path = modules[ 'ckeditor5/path' ];
+	load = modules[ 'ckeditor5/load' ];
+} );
+
+describe( 'load()', () => {
+	it( 'loads ckeditor.js', () => {
+		return load( 'ckeditor.js' )
+			.then( ( CKEDITORModule ) => {
+				expect( CKEDITORModule.default ).to.have.property( 'create' );
+			} );
+	} );
+
+	it( 'loads core/editor', () => {
+		return load( path.getModulePath( 'core/editor' ) )
+			.then( ( EditorModule ) => {
+				expect( EditorModule.default ).to.be.a( 'function' );
+			} );
+	} );
+} );
+

+ 9 - 8
tests/ckeditor/basepath.js

@@ -5,7 +5,12 @@
 
 'use strict';
 
-const modules = bender.amd.require( 'ckeditor' );
+const modules = bender.amd.require( 'ckeditor5/path' );
+let path;
+
+before( () => {
+	path = modules[ 'ckeditor5/path' ];
+} );
 
 beforeEach( () => {
 	// Ensure that no CKEDITOR_BASEPATH global is available.
@@ -24,22 +29,18 @@ describe( 'basePath', () => {
 	testGetBasePathFromTag( '../ckeditor/foo/ckeditor.JS', /\/ckeditor\/foo\/$/ );
 
 	it( 'should work with the CKEDITOR_BASEPATH global', () => {
-		const CKEDITOR = modules.ckeditor;
-
 		window.CKEDITOR_BASEPATH = 'http://foo.com/ckeditor/';
-		expect( CKEDITOR._getBasePath() ).to.equal( 'http://foo.com/ckeditor/' );
+		expect( path._getBasePath() ).to.equal( 'http://foo.com/ckeditor/' );
 	} );
 
 	function testGetBasePathFromTag( url, expectedBasePath ) {
 		it( 'should work with script tags - ' + url, () => {
-			const CKEDITOR = modules.ckeditor;
-
 			addScript( url );
 
 			if ( typeof expectedBasePath == 'string' ) {
-				expect( CKEDITOR._getBasePath() ).to.equal( expectedBasePath );
+				expect( path._getBasePath() ).to.equal( expectedBasePath );
 			} else {
-				expect( CKEDITOR._getBasePath() ).to.match( expectedBasePath );
+				expect( path._getBasePath() ).to.match( expectedBasePath );
 			}
 		} );
 	}

+ 45 - 0
tests/path/getmodulepath.js

@@ -0,0 +1,45 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const modules = bender.amd.require( 'ckeditor5/path' );
+let path;
+
+before( () => {
+	path = modules[ 'ckeditor5/path' ];
+} );
+
+describe( 'getModulePath()', () => {
+	it( 'generates path for the main file', () => {
+		const p = path.getModulePath( 'ckeditor' );
+
+		expect( p ).to.equal( 'ckeditor.js' );
+	} );
+
+	it( 'generates path for modules within ckeditor5 package', () => {
+		const p = path.getModulePath( 'ckeditor5/foo' );
+
+		expect( p ).to.equal( 'ckeditor5/foo.js' );
+	} );
+
+	it( 'generates path for modules within the core package', () => {
+		const p = path.getModulePath( 'core/ui/controller' );
+
+		expect( p ).to.equal( 'ckeditor5-core/ui/controller.js' );
+	} );
+
+	it( 'generates path for modules within some package', () => {
+		const p = path.getModulePath( 'some/ba' );
+
+		expect( p ).to.equal( 'ckeditor5-some/ba.js' );
+	} );
+
+	it( 'generates path from simplified feature name', () => {
+		const p = path.getModulePath( 'foo' );
+
+		expect( p ).to.equal( 'ckeditor5-foo/foo.js' );
+	} );
+} );

+ 0 - 31
tests/plugin/plugin.js

@@ -1,31 +0,0 @@
-/**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-describe( 'CKEDITOR.require()', () => {
-	it( 'should load a CKEditor plugin', ( done ) => {
-		CKEDITOR.require( [ 'plugin!devtest' ], ( DevTest ) => {
-			expect( DevTest ).to.have.property( 'isDevTest' );
-			done();
-		} );
-	} );
-
-	it( 'should load dependencies on CKEditor plugins', ( done ) => {
-		CKEDITOR.require( [ 'plugin!devtest/someclass' ], ( SomeClass ) => {
-			expect( SomeClass ).to.have.property( 'isSomeClass' );
-			done();
-		} );
-	} );
-
-	it( 'should load a dependency into a CKEditor plugin', ( done ) => {
-		CKEDITOR.require( [ 'plugin!devtest', 'plugin!devtest/someclass' ], ( DevTest, SomeClass ) => {
-			const test = new DevTest();
-
-			expect( test ).to.have.property( 'someProperty' ).to.be.an.instanceof( SomeClass );
-			done();
-		} );
-	} );
-} );