Files
nebula.js/commands/build/lib/config.js
2025-09-26 08:17:18 +02:00

240 lines
7.1 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const babel = require('@rollup/plugin-babel');
const postcss = require('rollup-plugin-postcss');
const replace = require('@rollup/plugin-replace');
const json = require('@rollup/plugin-json');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const terser = require('@rollup/plugin-terser');
const jsxPlugin = require('@babel/plugin-transform-react-jsx');
const babelPreset = require('@babel/preset-env');
const { visualizer } = require('rollup-plugin-visualizer');
const browsersList = require('@qlik/browserslist-config');
const babelPresetReact = require('@babel/preset-react');
const babelPresetTypescript = require('@babel/preset-typescript');
const resolveNative = require('./resolveNative');
const resolveReplacementStrings = (replacementStrings) => {
if (typeof replacementStrings !== 'object') {
throw new Error('replacementStrings should be an object with key value pairs of strings!');
}
return replacementStrings;
};
const setupReactNative = (argv) => {
const { reactNative } = argv;
let reactNativePath;
if (reactNative) {
reactNativePath = argv.reactNativePath || './react-native';
if (!fs.existsSync(`${reactNativePath}/package.json`)) {
// eslint-disable-next-line no-console
console.warn(
`WARNING: No ${reactNativePath}/package.json was found. If you really intended to build a react-native version of this package, please provide one.\nOther wise, to suppress this warning, omit the --reactNative flag.`
);
return false;
}
}
return { reactNative, reactNativePath };
};
const getBanner = ({ pkg }) => {
const { name, author, version, license } = pkg;
const auth = typeof author === 'object' ? `${author.name} <${author.email}>` : author || '';
return `/*
* ${name} v${version}
* Copyright (c) ${new Date().getFullYear()} ${auth}
* Released under the ${license} license.
*/
`;
};
const getExternalDefault = ({ pkg }) => {
const peers = pkg.peerDependencies || {};
return Object.keys(peers);
};
const getExternalCore = ({ pkg }) => {
const defaultExternal = ['@nebula.js/stardust', /^@qlik\/api\//];
const peers = Object.keys(pkg.peerDependencies || {});
return [...defaultExternal, ...peers];
};
const getOutputFileDefault = ({ pkg }) => pkg.main;
const getOutputNameDefault = ({ pkg }) => pkg.name.split('/').reverse()[0];
const config = ({
mode = 'production',
format = 'umd',
cwd = process.cwd(),
argv = { sourcemap: true, codeSplit: false, inlineDynamic: false },
core,
behaviours: {
getExternal = getExternalDefault,
getOutputFile = getOutputFileDefault,
getOutputName = getOutputNameDefault,
// Return false if no build should be done, otherwise true
enabled = () => true,
} = {},
} = {}) => {
const CWD = argv.cwd || cwd;
const { reactNative, reactNativePath } = setupReactNative(argv);
let dir = CWD;
let pkg = require(path.resolve(CWD, 'package.json')); // eslint-disable-line
const corePkg = core ? require(path.resolve(core, 'package.json')) : null; // eslint-disable-line
pkg = reactNative ? require(path.resolve(reactNativePath, 'package.json')) : pkg; // eslint-disable-line
const { sourcemap, replacementStrings = {}, typescript, preferBuiltins, browser } = argv;
const banner = getBanner({ pkg });
const outputName = getOutputName({ pkg, config: argv });
if (reactNative) {
dir = `${dir}/${reactNativePath}`;
} else if (corePkg) {
pkg = corePkg;
dir = core;
}
if (!enabled({ pkg })) {
return false;
}
const outputFile = getOutputFile({ pkg, config: argv });
const extensions = ['.mjs', '.js', '.jsx', '.json', '.node'];
let typescriptPlugin;
if (typescript) {
extensions.push('.tsx', '.ts');
try {
typescriptPlugin = require('@rollup/plugin-typescript'); // eslint-disable-line
} catch (e) {
throw new Error(`${e}\n '@rollup/plugin-typescript' is required to build using typescript.`);
}
}
const external = core ? getExternalCore({ pkg }) : getExternal({ pkg, config: argv });
// stardust should always be external
if (external.indexOf('@nebula.js/stardust') === -1) {
// eslint-disable-next-line no-console
console.warn('@nebula.js/stardust should be specified as a peer dependency');
external.push('@nebula.js/stardust');
}
const output = () => {
const outputConfig = {
banner,
format,
name: outputName,
sourcemap,
globals: {
'@nebula.js/stardust': 'stardust',
},
};
if (!argv.codeSplit || format === 'umd') {
outputConfig.file = path.resolve(dir, outputFile);
} else {
outputConfig.dir = path.resolve(dir, outputFile.split('/')[0]);
}
if (format === 'umd' || argv.inlineDynamic) {
outputConfig.inlineDynamicImports = true;
}
return outputConfig;
};
const babelPresets = [
[
babelPreset,
{
modules: false,
targets: {
browsers: browsersList,
},
},
],
[babelPresetReact],
];
if (typescript) {
babelPresets.push([
babelPresetTypescript,
{
allowNamespaces: true,
allowDeclareFields: true,
onlyRemoveTypeImports: true,
// Fixes for _default issues
isolatedModules: true,
},
]);
}
return {
input: {
onwarn(warning, warn) {
// Supress "use client" warnings coming from MUI bundling
if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes(`"use client"`)) {
return;
}
warn(warning);
},
input: path.resolve(CWD, 'src/index'),
external,
plugins: [
resolveNative({ reactNative }),
replace({
'process.env.NODE_ENV': JSON.stringify(mode === 'development' ? 'development' : 'production'),
preventAssignment: true,
...resolveReplacementStrings(replacementStrings),
}),
nodeResolve({
extensions,
browser,
preferBuiltins,
}),
postcss({
exclude: /\.module\.css$/,
}),
postcss({
include: /\.module\.css$/,
modules: true,
extract: true,
}),
commonjs({
ignoreTryCatch: false, // Avoids problems with require() inside try catch (https://github.com/rollup/plugins/issues/1004)
}),
json(),
// Handle all CSS with conditional modules processing
babel({
babelHelpers: 'bundled',
babelrc: false,
inputSourceMap: sourcemap,
extensions,
presets: babelPresets,
plugins: [[jsxPlugin]],
}),
...[
mode === 'production'
? terser({
output: {
preamble: banner,
},
})
: false,
],
...[
mode === 'development'
? visualizer({
filename: 'bundle-analysis.html',
})
: false,
],
].filter(Boolean),
},
output: output(),
};
};
module.exports = config;