Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/blue-laws-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-charts/legend': minor
---

Updated `@lg-charts/legend` package build configuration to generate both non-minified and minified bundles. The default export is now the non-minified bundle, with the minified bundle provided as a production-specific export.
9 changes: 9 additions & 0 deletions .changeset/breezy-groups-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@lg-tools/build': minor
---

Added a new exported `modernDevProdConfig` Rollup configuration, designed for component packages.

This configuration generates both minified and non-minified bundles to support production and development environments respectively. Please update the `exports` field in your `package.json` to include a `browser.production` entry for both `import` and `require` that points to the minified bundle (`[bundle-name]-min.js`). This ensures that consumers’ build tools use the optimized, minified bundle in production automatically.

The charts/legend package is the initial adopter of this configuration and is a good example of how to use this new configuration.
14 changes: 12 additions & 2 deletions charts/legend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,18 @@
".": {
"types": "./dist/types/index.d.ts",
"types@<=5.0": "./dist/types/ts4.9/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/umd/index.js"
"import": {
"browser": {
"production": "./dist/esm/index-min.js"
},
"default": "./dist/esm/index.js"
Comment on lines +44 to +48
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember: is import.browser.production necessary? Or can we just have import.production?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried import.production first, but the build:production script of MMS did not pick it up. Seems like it's a necessary structure to have the bundler use it.

},
"require": {
"browser": {
"production": "./dist/umd/index-min.js"
},
"default": "./dist/umd/index.js"
}
Comment on lines +44 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all my complaining about updating package.json files, I missed that this is necessary to get the dev builds. At least this is optional now though.

We should open a ticket to roll this out across the codebase

Copy link
Collaborator Author

@nima-taheri-mongodb nima-taheri-mongodb Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll create a ticket.
It's a quick job, except icon package that needs a bit of scripting.
I have good context, I can take that on and do it in a day. (not wise to say that ever 😂)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created two sub-tasks for the existing story.

  1. Experiment on a package | LG-5764
  2. Use the finalized approach on all packages | LG-5765

}
},
"typesVersions": {}
Expand Down
6 changes: 6 additions & 0 deletions charts/legend/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {
storiesConfig,
modernDevProdConfig,
} from '@lg-tools/build/config/rollup.config.mjs';

export default [storiesConfig, modernDevProdConfig];
19 changes: 13 additions & 6 deletions packages/icon/scripts/build/build-batch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* eslint-disable no-console */
import { MergedRollupOptions, rollup } from 'rollup';
import { InputPluginOption, rollup, type RollupOptions } from 'rollup';

import { GENERATED_DIR } from './constants';

async function getBatchBuildOptions(
batch: Array<string>,
): Promise<Array<MergedRollupOptions>> {
): Promise<Array<RollupOptions>> {
const { constructUMDGlobalName } = await import(
'@lg-tools/build/config/utils/constructUMDGlobalName.mjs'
);
Expand All @@ -21,11 +21,11 @@ async function getBatchBuildOptions(
{
...esmConfig,
input: batch.map(icon => `${GENERATED_DIR}/${icon}.tsx`),
output: [esmConfig.output],
output: esmConfig.output,
plugins: [
// Ensure @emotion packages are externalized (not bundled into icons)
nodeExternals({ deps: true, include: [/@emotion/] }),
...esmConfig.plugins,
...(esmConfig.plugins as Array<InputPluginOption>),
],
},
// UMD builds need a single input file
Expand All @@ -43,7 +43,7 @@ async function getBatchBuildOptions(
plugins: [
// Ensure @emotion packages are externalized (not bundled into icons)
nodeExternals({ deps: true, include: [/@emotion/] }),
...umdConfig.plugins,
...(umdConfig.plugins as Array<InputPluginOption>),
],
};
}),
Expand All @@ -64,7 +64,14 @@ export async function buildBatch(
for (const config of rollupConfigs) {
const bundle = await rollup(config);

await Promise.all(config.output.map(bundle.write));
if (config.output) {
const outputs = Array.isArray(config.output)
? config.output
: [config.output];

await Promise.all(outputs.map(bundle.write));
}

await bundle.close();
}
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions packages/icon/scripts/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ async function buildIcons(options: BuildIconOptions): Promise<void> {

new Command()
.description('Split icon files into batches for bundling in parallel')
.option('-f, --force', 'Force build all icons', false)
.option('-v, --verbose', 'Enable verbose output', false)
.action(buildIcons)
.parse();
161 changes: 108 additions & 53 deletions tools/build/config/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getUMDGlobals } from './utils/getUMDGlobals.mjs';
import { defaultsDeep } from 'lodash-es';

const extensions = ['.ts', '.tsx'];
const testUtilsFilename = 'src/testing/index.ts';
const testBundleGlob = 'src/testing/index.ts';
const storyGlob = 'src/*.stor{y,ies}.tsx';

const babelConfigPath = fileURLToPath(
Expand All @@ -33,43 +33,57 @@ const moduleFormatToDirectory = {
umd: 'dist/umd',
};

const doTestUtilsExist = glob.sync(testUtilsFilename).length > 0;
/**
* @param {{ format: import('rollup').OutputOptions['format'], useTerser?: boolean, outputFile?: string, outputName?: string, outputDir?: string }} options
* @returns {import('rollup').OutputOptions}
*/
const createOutput = ({
format,
useTerser = false,
outputFile = undefined,
outputName = '[name].js',
outputDir = moduleFormatToDirectory[format],
}) => {
return {
dir: outputDir,
file: outputFile,
name,
format,
sourcemap: true,
globals: format === 'umd' ? getUMDGlobals() : {},
validate: true,
interop: 'compat', // https://rollupjs.org/configuration-options/#output-interop
entryFileNames: outputName,
plugins: useTerser ? [terser()] : [],
};
};

/**
*
* @param {'esm' | 'umd'} format
* @param {*} overrides
* @returns
* @param {import('rollup').RollupOptions['output']} output
* @param {Partial<import('rollup').RollupOptions>} [overrides]
* @returns {import('rollup').RollupOptions}
*/
const createConfigForFormat = (format, overrides) => {
const createConfigForFormat = (output, overrides = {}) => {
/** @type {import('@rollup/plugin-babel').RollupBabelInputPluginOptions} */
const babelOptions = {
babelrc: false,
babelHelpers: 'bundled',
extensions,
configFile: babelConfigPath,
sourceMaps: true,
envName: 'production',
};

/** @type {import('rollup').RollupOptions} */
const formatConfig = {
input: ['src/index.ts'],
output: {
dir: moduleFormatToDirectory[format],
name,
format,
sourcemap: true,
globals: format === 'umd' ? getUMDGlobals() : {},
validate: true,
interop: 'compat', // https://rollupjs.org/configuration-options/#output-interop
},
output,
plugins: [
nodePolyfills(),
nodeExternals({ deps: true }),
nodeResolve({ extensions }),

babel({
babelrc: false,
babelHelpers: 'bundled',
extensions,
configFile: babelConfigPath,
sourceMaps: 'inline',
envName: 'production',
}),

babel(babelOptions),
svgr(),

terser(),
],
external,
strictDeprecations: true,
Expand All @@ -83,44 +97,85 @@ const createConfigForFormat = (format, overrides) => {
return finalConfig;
};

const esmConfig = createConfigForFormat('esm');
const umdConfig = createConfigForFormat('umd');
// 1. Create the default esm/umd bundles configs
const esmConfig = createConfigForFormat(
createOutput({ format: 'esm', useTerser: true }),
);
const umdConfig = createConfigForFormat(
createOutput({ format: 'umd', useTerser: true }),
);

const defaultConfig = [esmConfig, umdConfig];

// Add additional entry point to UMD build for test-utils if they exist
doTestUtilsExist &&
// 1.1. Create the modern dev/prod bundle configs
const modernDevProdConfig = createConfigForFormat([
createOutput({ format: 'esm' }),
createOutput({ format: 'umd' }),
createOutput({
format: 'esm',
useTerser: true,
outputName: '[name]-min.js',
}),
createOutput({
format: 'umd',
useTerser: true,
outputName: '[name]-min.js',
}),
]);

// 2. Create testing bundles (if applicable)
const testingBundleEntryPoints = glob.sync(testBundleGlob);

if (testingBundleEntryPoints.length > 0) {
defaultConfig.push(
createConfigForFormat('esm', {
input: testUtilsFilename,
output: {
dir: `${moduleFormatToDirectory['esm']}/testing`,
createConfigForFormat(
createOutput({
format: 'esm',
useTerser: true,
outputDir: `${moduleFormatToDirectory['esm']}/testing`,
}),
{
input: testingBundleEntryPoints,
},
}),
createConfigForFormat('umd', {
input: testUtilsFilename,
output: {
dir: `${moduleFormatToDirectory['umd']}/testing`,
),
createConfigForFormat(
createOutput({
format: 'umd',
useTerser: true,
outputDir: `${moduleFormatToDirectory['umd']}/testing`,
}),
{
input: testingBundleEntryPoints,
},
}),
),
);
}

// 3. Create stories bundles (if applicable)

const storiesEntryPoints = glob.sync(storyGlob);

// FIXME: Figure out a way to get rid of this.
// Creates a super-hacky `stories` bundle
const storiesExist = glob.sync(storyGlob).length > 0;
const storiesConfig = {
...esmConfig,
input: glob.sync(storyGlob)[0],
output: {
format: 'esm',
file: 'stories.js',
const storiesConfig = createConfigForFormat(
{
...createOutput({
format: 'esm',
useTerser: true,
outputDir: null,
outputFile: 'stories.js',
}),
sourcemap: false,
globals: esmConfig.output.globals,
},
};
{
input: storiesEntryPoints[0],
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a no change, we're ignoring all stories except the first one. doesn't feel right 😁 where do we use this?

},
);

storiesExist && defaultConfig.push(storiesConfig);
if (storiesEntryPoints.length > 0) {
defaultConfig.push(storiesConfig);
}

export { esmConfig, storiesConfig, umdConfig };
export { modernDevProdConfig, esmConfig, storiesConfig, umdConfig };

export default defaultConfig;
Loading