diff --git a/packages/ckeditor5-dev-utils/lib/builds/getdllpluginwebpackconfig.js b/packages/ckeditor5-dev-utils/lib/builds/getdllpluginwebpackconfig.js new file mode 100644 index 000000000..f17793f8e --- /dev/null +++ b/packages/ckeditor5-dev-utils/lib/builds/getdllpluginwebpackconfig.js @@ -0,0 +1,136 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +const path = require( 'path' ); +const webpack = require( 'webpack' ); +const TerserPlugin = require( 'terser-webpack-plugin' ); +const bundler = require( '../bundler' ); +const styles = require( '../styles' ); +const tools = require( '../tools' ); + +/** + * Returns a webpack configuration that creates a bundle file for the specified package. Thanks to that, plugins exported + * by the package can be added to ready-to-use builds. + * + * @param {Object} options + * @param {String} options.themePath An absolute path to the theme package. + * @param {String} options.packagePath An absolute path to the root directory of the package. + * @param {String} options.manifestPath An absolute path to the DLL manifest file. + * @param {Boolean} options.isDevelopmentMode Whether to build a dev mode of the package. + * @returns {Object} + */ +module.exports = function getDllPluginWebpackConfig( options ) { + const packageName = tools.readPackageName( options.packagePath ); + + const webpackConfig = { + mode: options.isDevelopmentMode ? 'development' : 'production', + + performance: { hints: false }, + + entry: path.join( options.packagePath, 'src', 'index.js' ), + + output: { + library: [ 'CKEditor5', getGlobalKeyForPackage( packageName ) ], + + path: path.join( options.packagePath, 'build' ), + filename: getIndexFileName( packageName ), + libraryTarget: 'umd', + libraryExport: 'default' + }, + + optimization: { + minimize: false + }, + + plugins: [ + new webpack.BannerPlugin( { + banner: bundler.getLicenseBanner(), + raw: true + } ), + new webpack.DllReferencePlugin( { + manifest: require( options.manifestPath ), + scope: 'ckeditor5/src', + name: 'CKEditor5.dll' + } ) + ], + + module: { + rules: [ + { + test: /\.svg$/, + use: [ 'raw-loader' ] + }, + { + test: /\.css$/, + use: [ + { + loader: 'style-loader', + options: { + injectType: 'singletonStyleTag', + attributes: { + 'data-cke': true + } + } + }, + { + loader: 'postcss-loader', + options: styles.getPostCssConfig( { + themeImporter: { + themePath: options.themePath + }, + minify: true + } ) + } + ] + } + ] + } + }; + + if ( options.isDevelopmentMode ) { + webpackConfig.devtool = 'source-map'; + } else { + webpackConfig.optimization.minimize = true; + + webpackConfig.optimization.minimizer = [ + new TerserPlugin( { + terserOptions: { + output: { + // Preserve CKEditor 5 license comments. + comments: /^!/ + } + }, + extractComments: false + } ) + ]; + } + + return webpackConfig; +}; + +/** + * Transforms the package name (`@ckeditor/ckeditor5-foo-bar`) to the name that will be used while + * exporting the library into the global scope. + * + * @param {String} packageName + * @returns {String} + */ +function getGlobalKeyForPackage( packageName ) { + return packageName + .replace( /^@ckeditor\/ckeditor5?-/, '' ) + .replace( /-([a-z])/g, ( match, p1 ) => p1.toUpperCase() ); +} + +/** + * Extracts the main file name from the package name. + * + * @param packageName + * @returns {String} + */ +function getIndexFileName( packageName ) { + return packageName.replace( /^@ckeditor\/ckeditor5?-/, '' ) + '.js'; +} diff --git a/packages/ckeditor5-dev-utils/lib/builds/index.js b/packages/ckeditor5-dev-utils/lib/builds/index.js new file mode 100644 index 000000000..ea009b718 --- /dev/null +++ b/packages/ckeditor5-dev-utils/lib/builds/index.js @@ -0,0 +1,10 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +module.exports = { + getDllPluginWebpackConfig: require( './getdllpluginwebpackconfig' ) +}; diff --git a/packages/ckeditor5-dev-utils/lib/index.js b/packages/ckeditor5-dev-utils/lib/index.js index 325e6c6cf..07f674042 100644 --- a/packages/ckeditor5-dev-utils/lib/index.js +++ b/packages/ckeditor5-dev-utils/lib/index.js @@ -13,5 +13,6 @@ module.exports = { workspace: require( './workspace' ), translations: require( './translations/index' ), bundler: require( './bundler/index' ), + builds: require( './builds/index' ), styles: require( './styles/index' ) }; diff --git a/packages/ckeditor5-dev-utils/package.json b/packages/ckeditor5-dev-utils/package.json index 79b232201..3d99a9128 100644 --- a/packages/ckeditor5-dev-utils/package.json +++ b/packages/ckeditor5-dev-utils/package.json @@ -16,9 +16,13 @@ "pofile": "^1.0.9", "postcss": "^7.0.17", "postcss-import": "^12.0.0", + "postcss-loader": "^3.0.0", "postcss-mixins": "^6.2.0", "postcss-nesting": "^7.0.0", + "raw-loader": "^4.0.1", "shelljs": "^0.8.1", + "style-loader": "^1.2.1", + "terser-webpack-plugin": "^3.0.2", "through2": "^3.0.1" }, "devDependencies": { diff --git a/packages/ckeditor5-dev-utils/tests/builds/getdllpluginwebpackconfig.js b/packages/ckeditor5-dev-utils/tests/builds/getdllpluginwebpackconfig.js new file mode 100644 index 000000000..f768fe2d7 --- /dev/null +++ b/packages/ckeditor5-dev-utils/tests/builds/getdllpluginwebpackconfig.js @@ -0,0 +1,123 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +const path = require( 'path' ); +const chai = require( 'chai' ); +const sinon = require( 'sinon' ); +const mockery = require( 'mockery' ); +const webpack = require( 'webpack' ); +const TerserPlugin = require( 'terser-webpack-plugin' ); +const expect = chai.expect; + +describe( 'builds/getDllPluginWebpackConfig()', () => { + let sandbox, stubs, getDllPluginWebpackConfig; + + const manifest = { + content: { + '../../node_modules/lodash-es/_DataView.js': { + id: '../../node_modules/lodash-es/_DataView.js', + buildMeta: { + buildMeta: 'namespace', + providedExports: [ + 'default' + ] + } + } + } + }; + + beforeEach( () => { + sandbox = sinon.createSandbox(); + + stubs = { + tools: { + readPackageName: sandbox.stub() + } + }; + + sandbox.stub( path, 'join' ).callsFake( ( ...args ) => args.join( '/' ) ); + + mockery.enable( { + useCleanCache: true, + warnOnReplace: false, + warnOnUnregistered: false + } ); + + mockery.registerMock( '../tools', stubs.tools ); + mockery.registerMock( '/manifest/path', manifest ); + + getDllPluginWebpackConfig = require( '../../lib/builds/getdllpluginwebpackconfig' ); + } ); + + afterEach( () => { + mockery.disable(); + sandbox.restore(); + } ); + + it( 'returns the webpack configuration in production mode by default', () => { + stubs.tools.readPackageName.returns( '@ckeditor/ckeditor5-dev' ); + + const webpackConfig = getDllPluginWebpackConfig( { + packagePath: '/package/path', + themePath: '/theme/path', + manifestPath: '/manifest/path' + } ); + + expect( webpackConfig ).to.be.an( 'object' ); + + expect( webpackConfig.mode ).to.equal( 'production' ); + + expect( webpackConfig.entry ).to.equal( '/package/path/src/index.js' ); + expect( webpackConfig.output.library ).to.deep.equal( [ 'CKEditor5', 'dev' ] ); + expect( webpackConfig.output.path ).to.equal( '/package/path/build' ); + expect( webpackConfig.output.filename ).to.equal( 'dev.js' ); + + expect( webpackConfig.plugins ).to.be.an( 'array' ); + expect( webpackConfig.plugins.length ).to.equal( 2 ); + expect( webpackConfig.plugins[ 1 ] ).to.be.an.instanceOf( webpack.DllReferencePlugin ); + + expect( webpackConfig.plugins[ 1 ].options.manifest ).to.deep.equal( manifest ); + expect( webpackConfig.plugins[ 1 ].options.scope ).to.equal( 'ckeditor5/src' ); + expect( webpackConfig.plugins[ 1 ].options.name ).to.equal( 'CKEditor5.dll' ); + + expect( webpackConfig.optimization.minimize ).to.equal( true ); + expect( webpackConfig.optimization.minimizer ).to.be.an( 'array' ); + expect( webpackConfig.optimization.minimizer.length ).to.equal( 1 ); + + // Due to versions mismatch, the `instanceof` check does not pass. + expect( webpackConfig.optimization.minimizer[ 0 ].constructor.name ).to.equal( TerserPlugin.name ); + } ); + + it( 'transforms package with many dashes in its name', () => { + stubs.tools.readPackageName.returns( '@ckeditor/ckeditor5-html-embed' ); + + const webpackConfig = getDllPluginWebpackConfig( { + packagePath: '/package/path', + themePath: '/theme/path', + manifestPath: '/manifest/path' + } ); + + expect( webpackConfig ).to.be.an( 'object' ); + expect( webpackConfig.output.library ).to.deep.equal( [ 'CKEditor5', 'htmlEmbed' ] ); + expect( webpackConfig.output.filename ).to.equal( 'html-embed.js' ); + } ); + + it( 'does not minify the destination file when in dev mode', () => { + stubs.tools.readPackageName.returns( '@ckeditor/ckeditor5-dev' ); + + const webpackConfig = getDllPluginWebpackConfig( { + packagePath: '/package/path', + themePath: '/theme/path', + manifestPath: '/manifest/path', + isDevelopmentMode: true + } ); + + expect( webpackConfig.mode ).to.equal( 'development' ); + expect( webpackConfig.optimization.minimize ).to.equal( false ); + expect( webpackConfig.optimization.minimizer ).to.be.undefined; + } ); +} ); diff --git a/packages/ckeditor5-dev-utils/tests/builds/index.js b/packages/ckeditor5-dev-utils/tests/builds/index.js new file mode 100644 index 000000000..65e7bd796 --- /dev/null +++ b/packages/ckeditor5-dev-utils/tests/builds/index.js @@ -0,0 +1,23 @@ +/** + * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +const chai = require( 'chai' ); +const expect = chai.expect; + +describe( 'builds', () => { + let tasks; + + beforeEach( () => { + tasks = require( '../../lib/builds/index' ); + } ); + + describe( 'getDllPluginWebpackConfig()', () => { + it( 'should be a function', () => { + expect( tasks.getDllPluginWebpackConfig ).to.be.a( 'function' ); + } ); + } ); +} );