Frontend: use custom conditions for development and build (#111685)

* build(frontend): enable custom condition for resolving source files during dev and build

* feat(packages): apply conditional name to export properties

* chore(packages): add standard exports to flamegraph and prometheus

* chore(packages): resolve main, module, types to built files

* build(packages): clean up prepare-npm-package for custom condition changes

* refactor(packages): reduce repetition in conditional exports

* build(storybook): add @grafana-app/source to conditionNames

* test(frontend): add grafana-app/source customCondition for jest tests

* refactor(frontend): remove nested package import paths

* chore(jest): use customExportConditions for source files and browser

* chore(i18n): use src for ./eslint-plugin export

* chore(packages): set packages tsconfigs to moduleResolution bundler

* chore(packages): fix rollup builds

* build(packages): build cjs as multiple files

* chore(sql): reference MonitoringLogger for moduleresolution bundler to pass typecheck

* chore(ui): add type refs for moduleresolution bundler to pass typecheck

* feat(schema): add exports for cleaner import paths

* refactor(frontend): clean up schema paths to point to exports instead of nested file paths

* build(storybook): hack the builder-manager for custom conditions to resolve

* build(decoupled-plugins): fix broken builds due to missing conditionNames

* chore(e2e): pass condition to playwright to resolve local packages

* build(frontend): fix failing build

* chore(select): fix typings

* style(frontend): clean up eslint suppressions

* chore(packages): fix type errors due to incorrect tsconfig settings

* build(generate-apis): use swc with ts-node and moduleResolution bundler

* chore(cypress): add conditionNames to resolve monorepo packages

* build(npm): update prepare to work with latest exports changes

* build(packages): fix prepare-npm-package script

* fix(e2e-selectors): update debugoverlay for data-testid change

* build(packages): stop editing package.json at pack n publish time

* rerun ci

* chore(api-clients): use moduleResolution: bundler for customConditions support

* chore(api-clients): fix generation

* build(packages): remove aliasing exports, remove exports with only customConditions

* Revert "refactor(frontend): clean up schema paths to point to exports instead of nested file paths"

This reverts commit 7949b6ea0e60e51989d2a8149b7a24647cd68916.

* revert(schema): remove exports from package so builds work

* build(api-clients): fix up api-clients exports and rollup config

* build(api-clients): Update generated package exports for api clients

* build(schema): add overrides to cjsOutput and esmOutput so built directory structure is correct

* fix(packages): use rootDirs to prevent types/src directories in built d.ts file paths

* build(packages): prevent empty exports added to package.json during pack

* docs(packages): update readme with custom conditions information

---------

Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com>
This commit is contained in:
Jack Westbrook
2025-12-18 11:47:38 +01:00
committed by GitHub
co-authored by Tom Ratcliffe
parent 2123099e88
commit 5bedcc7bd7
79 changed files with 440 additions and 365 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "commonjs"
"moduleResolution": "nodenext",
"module": "NodeNext"
},
"extends": "../../tsconfig.json",
"ts-node": {
+19 -92
View File
@@ -1,108 +1,35 @@
//@ts-check
import PackageJson from '@npmcli/package-json';
import { mkdir } from 'node:fs/promises';
const cwd = process.cwd();
try {
const pkgJson = await PackageJson.load(cwd);
const cjsIndex = pkgJson.content.publishConfig?.main ?? pkgJson.content.main;
const esmIndex = pkgJson.content.publishConfig?.module ?? pkgJson.content.module;
const typesIndex = pkgJson.content.publishConfig?.types ?? pkgJson.content.types;
const pkgJsonExports = pkgJson.content.exports;
const exports = {
'./package.json': './package.json',
'.': {
import: {
types: typesIndex,
default: esmIndex,
},
require: {
types: typesIndex,
default: cjsIndex,
},
},
};
// Fix so scenes can access `@grafana/schema` nested dist import paths e.g.
// import {} from '@grafana/schema/dist/esm/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen'
if (pkgJson.content.name === '@grafana/schema') {
exports['./dist/*'] = {
types: './dist/*',
default: './dist/*',
};
}
// Fix for @grafana/i18n so eslint-plugin can be imported by consumers
if (pkgJson.content.name === '@grafana/i18n') {
exports['./eslint-plugin'] = {
types: './dist/eslint/index.d.ts',
import: './dist/eslint/index.cjs',
require: './dist/eslint/index.cjs',
};
}
pkgJson.update({
main: cjsIndex,
types: typesIndex,
module: esmIndex,
exports,
});
await pkgJson.save();
// If an alias package name is provided we add an exports entry for the alias
// then generate an additional "nested" package.json for typescript resolution that
// doesn't use the exports property in package.json.
if (process.env.ALIAS_PACKAGE_NAME) {
const aliasNames = process.env.ALIAS_PACKAGE_NAME.split(',');
const additionalExports = aliasNames.reduce((acc, alias) => {
acc[`./${alias}`] = {
import: {
types: typesIndex.replace('index', alias),
default: esmIndex.replace('index', alias),
},
require: {
types: typesIndex.replace('index', alias),
default: cjsIndex.replace('index', alias),
},
};
return acc;
}, {});
// skip packages without exports otherwise consumers cannot import anything from the package
if (pkgJsonExports && typeof pkgJsonExports === 'object') {
// Remove all exports that only contain a single key '@grafana-app/source'
// as these will not resolve when validating packages with attw because the
// source code is not available in the tarball.
for (const [key, val] of Object.entries(pkgJsonExports)) {
if (
val !== null &&
typeof val === 'object' &&
Object.keys(val).length === 1 &&
Object.keys(val)[0] === '@grafana-app/source'
) {
delete pkgJsonExports[key];
}
}
pkgJson.update({
exports: {
...pkgJson.content.exports,
...additionalExports,
},
files: [...pkgJson.content.files, ...aliasNames],
exports: pkgJsonExports,
});
await pkgJson.save();
for await (const aliasName of aliasNames) {
await createAliasPackageJsonFiles(pkgJson.content, aliasName);
}
await pkgJson.save();
}
} catch (e) {
console.error(e);
process.exit(1);
}
async function createAliasPackageJsonFiles(packageJsonContent, aliasName) {
const pkgName = `${packageJsonContent.name}/${aliasName}`;
try {
console.log(`📦 Writing alias package.json for ${pkgName}.`);
const pkgJsonPath = `${cwd}/${aliasName}`;
await mkdir(pkgJsonPath, { recursive: true });
const pkgJson = await PackageJson.create(pkgJsonPath, {
data: {
name: pkgName,
types: `../dist/types/${aliasName}.d.ts`,
main: `../dist/cjs/${aliasName}.cjs`,
module: `../dist/esm/${aliasName}.mjs`,
},
});
await pkgJson.save();
} catch (error) {
throw new Error(`Error generating package.json for ${pkgName}`, error);
}
}
+1
View File
@@ -4,6 +4,7 @@
"alwaysStrict": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"customConditions": ["@grafana-app/source"],
"downlevelIteration": true,
"esModuleInterop": true,
"experimentalDecorators": true,
+1 -1
View File
@@ -17,7 +17,7 @@ for file in "$ARTIFACTS_DIR"/*.tgz; do
fi
# shellcheck disable=SC2086
if ! yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then
if ! NODE_OPTIONS="-C @grafana-app/source" yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then
echo "attw check failed for $file"
echo ""
failed_checks+=("$file - yarn attw")
+1
View File
@@ -22,6 +22,7 @@ module.exports = {
publicPath: 'public/build/',
},
resolve: {
conditionNames: ['@grafana-app/source', '...'],
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
alias: {
// some of data source plugins use global Prism object to add the language definition