diff --git a/packages/ckeditor5-dev-build-tools/src/build.ts b/packages/ckeditor5-dev-build-tools/src/build.ts index 615dfdaa2..2814c84e0 100644 --- a/packages/ckeditor5-dev-build-tools/src/build.ts +++ b/packages/ckeditor5-dev-build-tools/src/build.ts @@ -9,8 +9,9 @@ import util from 'util'; import chalk from 'chalk'; import path from 'upath'; import { rollup, type RollupOutput, type GlobalsOption } from 'rollup'; +import { loadSourcemaps } from './plugins/loadSourcemaps.js'; import { getRollupConfig } from './config.js'; -import { getCwdPath, camelizeObjectKeys, removeWhitespace } from './utils.js'; +import { getCwdPath, camelizeObjectKeys, removeWhitespace, getOptionalPlugin } from './utils.js'; export interface BuildOptions { input: string; @@ -104,11 +105,22 @@ async function generateUmdBuild( args: BuildOptions, bundle: RollupOutput ): Pro const { dir, name } = path.parse( args.output ); const { plugins, ...config } = await getRollupConfig( args ); - const build = await rollup( config ); - const globals = { - ...CKEDITOR_GLOBALS, - ...args.globals as GlobalsOption - }; + + /** + * Ignore the plugins we used for the ESM build. Instead, add a new plugin to not only + * load the source code of the dependencies (which is the default in Rollup for better + * performance), but also their source maps to generate a proper final source map for + * the UMD bundle. + */ + const build = await rollup( { + ...config, + plugins: [ + getOptionalPlugin( + args.sourceMap, + loadSourcemaps() + ) + ] + } ); const umdBundle = await build.write( { format: 'umd', @@ -116,7 +128,10 @@ async function generateUmdBuild( args: BuildOptions, bundle: RollupOutput ): Pro assetFileNames: '[name][extname]', sourcemap: args.sourceMap, name: args.name, - globals + globals: { + ...CKEDITOR_GLOBALS, + ...args.globals as GlobalsOption + } } ); return { diff --git a/packages/ckeditor5-dev-build-tools/src/config.ts b/packages/ckeditor5-dev-build-tools/src/config.ts index 28c159789..586d4a46e 100644 --- a/packages/ckeditor5-dev-build-tools/src/config.ts +++ b/packages/ckeditor5-dev-build-tools/src/config.ts @@ -5,9 +5,9 @@ import path from 'upath'; import { existsSync } from 'fs'; -import { getUserDependency } from './utils.js'; +import { getOptionalPlugin, getUserDependency } from './utils.js'; import type { PackageJson } from 'type-fest'; -import type { InputPluginOption, Plugin, RollupOptions } from 'rollup'; +import type { Plugin, RollupOptions } from 'rollup'; import type { BuildOptions } from './build.js'; /** @@ -272,13 +272,6 @@ export async function getRollupConfig( options: BuildOptions ) { } as const satisfies RollupOptions; } -/** - * Returns plugin if condition is truthy. This is used only to get the types right. - */ -function getOptionalPlugin( condition: unknown, plugin: T ): T | undefined { - return condition ? plugin : undefined; -} - /** * Returns a list of keys in `package.json` file of a given dependency. */ diff --git a/packages/ckeditor5-dev-build-tools/src/index.ts b/packages/ckeditor5-dev-build-tools/src/index.ts index 162b40003..98846c2a9 100644 --- a/packages/ckeditor5-dev-build-tools/src/index.ts +++ b/packages/ckeditor5-dev-build-tools/src/index.ts @@ -6,6 +6,7 @@ export { build } from './build.js'; export { addBanner, type RollupBannerOptions } from './plugins/banner.js'; export { emitCss, type RollupEmitCssOptions } from './plugins/emitCss.js'; +export { loadSourcemaps } from './plugins/loadSourcemaps.js'; export { replaceImports, type RollupReplaceOptions } from './plugins/replace.js'; export { splitCss, type RollupSplitCssOptions } from './plugins/splitCss.js'; export { translations, type RollupTranslationsOptions } from './plugins/translations.js'; diff --git a/packages/ckeditor5-dev-build-tools/src/plugins/loadSourcemaps.ts b/packages/ckeditor5-dev-build-tools/src/plugins/loadSourcemaps.ts new file mode 100644 index 000000000..73a88719a --- /dev/null +++ b/packages/ckeditor5-dev-build-tools/src/plugins/loadSourcemaps.ts @@ -0,0 +1,25 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import fs from 'fs'; +import type { Plugin } from 'rollup'; + +export function loadSourcemaps(): Plugin { + return { + name: 'cke5-load-sourcemaps', + load( id: string ) { + const sourceMapId = id + '.map'; + + if ( !fs.existsSync( sourceMapId ) ) { + return; + } + + return { + code: fs.readFileSync( id, 'utf-8' ), + map: fs.readFileSync( sourceMapId, 'utf-8' ) + }; + } + }; +} diff --git a/packages/ckeditor5-dev-build-tools/src/utils.ts b/packages/ckeditor5-dev-build-tools/src/utils.ts index 93eb29161..2933065cf 100644 --- a/packages/ckeditor5-dev-build-tools/src/utils.ts +++ b/packages/ckeditor5-dev-build-tools/src/utils.ts @@ -6,6 +6,7 @@ import { createRequire } from 'module'; import path from 'upath'; import type { CamelCase, CamelCasedProperties } from 'type-fest'; +import type { InputPluginOption } from 'rollup'; const require = createRequire( import.meta.url ); @@ -60,3 +61,10 @@ export function getUserDependency( name: string ): any { return require( path ); } + +/** + * Returns plugin if condition is truthy. This is used only to get the types right. + */ +export function getOptionalPlugin( condition: unknown, plugin: T ): T | undefined { + return condition ? plugin : undefined; +} diff --git a/packages/ckeditor5-dev-build-tools/tests/_utils/utils.ts b/packages/ckeditor5-dev-build-tools/tests/_utils/utils.ts index d45288f19..52873193b 100644 --- a/packages/ckeditor5-dev-build-tools/tests/_utils/utils.ts +++ b/packages/ckeditor5-dev-build-tools/tests/_utils/utils.ts @@ -4,7 +4,8 @@ */ import { expect, vi } from 'vitest'; -import type { RollupOutput, OutputChunk, OutputAsset } from 'rollup'; +import swc from '@rollup/plugin-swc'; +import type { RollupOutput, OutputChunk, OutputAsset, Plugin } from 'rollup'; import * as utils from '../../src/utils'; /** @@ -71,3 +72,20 @@ export async function mockGetUserDependency( path: string, cb: () => any ): Prom return actualImport( url ); } ); } + +/** + * Helper function for getting a preconfigured `swc` plugin. + */ +export function swcPlugin(): Plugin { + return swc( { + include: [ '**/*.[jt]s' ], + swc: { + jsc: { + target: 'es2019' + }, + module: { + type: 'es6' + } + } + } ); +} diff --git a/packages/ckeditor5-dev-build-tools/tests/plugins/banner/banner.test.ts b/packages/ckeditor5-dev-build-tools/tests/plugins/banner/banner.test.ts index ad0bf055f..bfac5246a 100644 --- a/packages/ckeditor5-dev-build-tools/tests/plugins/banner/banner.test.ts +++ b/packages/ckeditor5-dev-build-tools/tests/plugins/banner/banner.test.ts @@ -5,10 +5,9 @@ import { join } from 'path'; import { test, expect } from 'vitest'; -import swc from '@rollup/plugin-swc'; import styles from 'rollup-plugin-styles'; import { rollup, type RollupOutput, type OutputAsset } from 'rollup'; -import { verifyAsset, verifyChunk } from '../../_utils/utils.js'; +import { swcPlugin, verifyAsset, verifyChunk } from '../../_utils/utils.js'; import { addBanner, type RollupBannerOptions } from '../../../src/index.js'; @@ -19,17 +18,7 @@ async function generateBundle( options: RollupBannerOptions, sourcemap: boolean const bundle = await rollup( { input: join( import.meta.dirname, './fixtures/input.ts' ), plugins: [ - swc( { - include: [ '**/*.[jt]s' ], - swc: { - jsc: { - target: 'es2019' - }, - module: { - type: 'es6' - } - } - } ), + swcPlugin, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/ckeditor5-dev-build-tools/tests/plugins/emitCss/emitCss.test.ts b/packages/ckeditor5-dev-build-tools/tests/plugins/emitCss/emitCss.test.ts index 1a32dccb4..7db22fa61 100644 --- a/packages/ckeditor5-dev-build-tools/tests/plugins/emitCss/emitCss.test.ts +++ b/packages/ckeditor5-dev-build-tools/tests/plugins/emitCss/emitCss.test.ts @@ -5,10 +5,9 @@ import { join } from 'path'; import { test } from 'vitest'; -import swc from '@rollup/plugin-swc'; import styles from 'rollup-plugin-styles'; import { rollup, type RollupOutput } from 'rollup'; -import { verifyAsset } from '../../_utils/utils.js'; +import { swcPlugin, verifyAsset } from '../../_utils/utils.js'; import { emitCss } from '../../../src/index.js'; @@ -16,17 +15,7 @@ async function generateBundle( input: string ): Promise const bundle = await rollup( { input: join( import.meta.dirname, input ), plugins: [ - swc( { - include: [ '**/*.[jt]s' ], - swc: { - jsc: { - target: 'es2019' - }, - module: { - type: 'es6' - } - } - } ), + swcPlugin, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/input.ts b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/input.ts new file mode 100644 index 000000000..db3dc6310 --- /dev/null +++ b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/input.ts @@ -0,0 +1,7 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +// The `magic-string` package is used because it contains source maps. +export { default as MagicString } from 'magic-string'; diff --git a/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/tsconfig.json b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/tsconfig.json new file mode 100644 index 000000000..1717f1a38 --- /dev/null +++ b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/fixtures/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": [ + "**/*" + ] +} diff --git a/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/loadSourcemaps.test.ts b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/loadSourcemaps.test.ts new file mode 100644 index 000000000..7d770bc82 --- /dev/null +++ b/packages/ckeditor5-dev-build-tools/tests/plugins/loadSourcemaps/loadSourcemaps.test.ts @@ -0,0 +1,46 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { join } from 'path'; +import { test, expect } from 'vitest'; +import { rollup, type RollupOutput, type OutputAsset } from 'rollup'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import { swcPlugin } from '../../_utils/utils.js'; + +import { loadSourcemaps } from '../../../src/index.js'; +import { getOptionalPlugin } from '../../../src/utils.js'; + +async function generateBundle( input: string, sourcemap: boolean = false ): Promise { + const bundle = await rollup( { + input: join( import.meta.dirname, input ), + plugins: [ + nodeResolve(), + swcPlugin, + getOptionalPlugin( sourcemap, loadSourcemaps() ) + ] + } ); + + const { output } = await bundle.generate( { + format: 'esm', + file: 'input.js', + assetFileNames: '[name][extname]', + sourcemap + } ); + + return output; +} + +test( 'Emits source maps combined with source maps of dependencies', async () => { + const output = await generateBundle( './fixtures/input.ts', true ); + const sourceMap = output.find( asset => asset.fileName === 'input.js.map' ) as OutputAsset; + + /** + * The resulting source map will only contain the `/magic-string/src/` string if the source map + * of the `magic-string` dependency was loaded and combined with the source map of the input file. + * Otherwise, the source map will contain the `/magic-string/dist/` string, which is the bundled + * build of the `magic-string` dependency. + */ + expect( sourceMap.source ).toContain( '/magic-string/src/' ); +} );