Skip to content

Instantly share code, notes, and snippets.

@ShayDavidson
Created May 16, 2023 05:43
Show Gist options
  • Save ShayDavidson/e89751a5bc57a17ca6846dfd68aabf45 to your computer and use it in GitHub Desktop.
Save ShayDavidson/e89751a5bc57a17ca6846dfd68aabf45 to your computer and use it in GitHub Desktop.
Override rollup executor
{
"builders": {
"rollup": {
"implementation": "./impl",
"schema": "./schema.json",
"description": "Custom rollup executor"
}
}
}
"use strict";
// THIS IS DUE TO THE FACT NX TEAM DIDN'T ADD A SIMPLE CONDITION TO ALLOW TYPE GENERATION
// https://github.com/nrwl/nx/issues/9059
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRollupOptions = exports.rollupExecutor = void 0;
const tslib_1 = require("tslib");
require("dotenv/config");
const ts = require("typescript");
const rollup = require("rollup");
const peerDepsExternal = require("rollup-plugin-peer-deps-external");
const plugin_babel_1 = require("@rollup/plugin-babel");
const path_1 = require("path");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const rxjs_for_await_1 = require("@nx/devkit/src/utils/rxjs-for-await");
const autoprefixer = require("autoprefixer");
const devkit_1 = require("@nx/devkit");
const buildable_libs_utils_1 = require("@nx/js/src/utils/buildable-libs-utils");
const plugin_node_resolve_1 = require("@rollup/plugin-node-resolve");
const run_rollup_1 = require("@nx/rollup/src/executors/rollup/lib/run-rollup");
const normalize_1 = require("@nx/rollup/src/executors/rollup/lib/normalize");
const analyze_plugin_1 = require("@nx/rollup/src/executors/rollup/lib/analyze-plugin");
const fs_1 = require("@nx/rollup/src/utils/fs");
const swc_plugin_1 = require("@nx/rollup/src/executors/rollup/lib/swc-plugin");
const validate_types_1 = require("@nx/rollup/src/executors/rollup/lib//validate-types");
const update_package_json_1 = require("@nx/rollup/src/executors/rollup/lib//update-package-json");
// These use require because the ES import isn't correct.
const commonjs = require('@rollup/plugin-commonjs');
const image = require('@rollup/plugin-image');
const json = require('@rollup/plugin-json');
const copy = require('rollup-plugin-copy');
const postcss = require('rollup-plugin-postcss');
const fileExtensions = ['.js', '.jsx', '.ts', '.tsx'];
function rollupExecutor(rawOptions, context) {
var _a, _b;
var _c;
return tslib_1.__asyncGenerator(this, arguments, function* rollupExecutor_1() {
(_a = (_c = process.env).NODE_ENV) !== null && _a !== void 0 ? _a : (_c.NODE_ENV = 'production');
const project = context.projectsConfigurations.projects[context.projectName];
const sourceRoot = project.sourceRoot;
const { target, dependencies } = (0, buildable_libs_utils_1.calculateProjectDependencies)(context.projectGraph, context.root, context.projectName, context.targetName, context.configurationName, true);
const options = (0, normalize_1.normalizeRollupExecutorOptions)(rawOptions, context.root, sourceRoot);
const packageJson = (0, devkit_1.readJsonFile)(options.project);
const npmDeps = ((_b = context.projectGraph.dependencies[context.projectName]) !== null && _b !== void 0 ? _b : [])
.filter((d) => d.target.startsWith('npm:'))
.map((d) => d.target.slice(4));
const rollupOptions = createRollupOptions(options, dependencies, context, packageJson, sourceRoot, npmDeps);
const outfile = resolveOutfile(context, options);
if (options.compiler === 'swc') {
try {
yield tslib_1.__await((0, validate_types_1.validateTypes)({
workspaceRoot: context.root,
projectRoot: options.projectRoot,
tsconfig: options.tsConfig,
}));
}
catch (_d) {
return yield tslib_1.__await({ success: false });
}
}
if (options.watch) {
const watcher = rollup.watch(rollupOptions);
return yield tslib_1.__await(yield tslib_1.__await(yield* tslib_1.__asyncDelegator(tslib_1.__asyncValues((0, rxjs_for_await_1.eachValueFrom)(new rxjs_1.Observable((obs) => {
watcher.on('event', (data) => {
if (data.code === 'START') {
devkit_1.logger.info(`Bundling ${context.projectName}...`);
}
else if (data.code === 'END') {
(0, update_package_json_1.updatePackageJson)(options, context, target, dependencies, packageJson);
devkit_1.logger.info('Bundle complete. Watching for file changes...');
obs.next({ success: true, outfile });
}
else if (data.code === 'ERROR') {
devkit_1.logger.error(`Error during bundle: ${data.error.message}`);
obs.next({ success: false });
}
});
// Teardown logic. Close watcher when unsubscribed.
return () => watcher.close();
}))))));
}
else {
devkit_1.logger.info(`Bundling ${context.projectName}...`);
// Delete output path before bundling
if (options.deleteOutputPath) {
(0, fs_1.deleteOutputDir)(context.root, options.outputPath);
}
const start = process.hrtime.bigint();
return yield tslib_1.__await((0, rxjs_1.from)(rollupOptions)
.pipe((0, operators_1.concatMap)((opts) => (0, run_rollup_1.runRollup)(opts).pipe((0, operators_1.catchError)((e) => {
devkit_1.logger.error(`Error during bundle: ${e}`);
return (0, rxjs_1.of)({ success: false });
}))), (0, operators_1.scan)((acc, result) => {
if (!acc.success)
return acc;
return result;
}, { success: true, outfile }), (0, operators_1.last)(), (0, operators_1.tap)({
next: (result) => {
if (result.success) {
const end = process.hrtime.bigint();
const duration = `${(Number(end - start) / 1000000000).toFixed(2)}s`;
(0, update_package_json_1.updatePackageJson)(options, context, target, dependencies, packageJson);
devkit_1.logger.info(`⚡ Done in ${duration}`);
}
else {
devkit_1.logger.error(`Bundle failed: ${context.projectName}`);
}
},
}))
.toPromise());
}
});
}
exports.rollupExecutor = rollupExecutor;
// -----------------------------------------------------------------------------
function createRollupOptions(options, dependencies, context, packageJson, sourceRoot, npmDeps) {
const useBabel = options.compiler === 'babel';
const useTsc = options.compiler === 'tsc';
const useSwc = options.compiler === 'swc';
const tsConfigPath = (0, devkit_1.joinPathFragments)(context.root, options.tsConfig);
const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
const config = ts.parseJsonConfigFileContent(configFile.config, ts.sys, (0, path_1.dirname)(tsConfigPath));
if (!options.format || !options.format.length) {
options.format = readCompatibleFormats(config);
}
return options.format.map((format, idx) => {
const plugins = [
copy({
targets: convertCopyAssetsToRollupOptions(options.outputPath, options.assets),
}),
image(),
json(),
require('rollup-plugin-typescript2')({
check: !options.skipTypeCheck,
tsconfig: options.tsConfig,
tsconfigOverride: {
compilerOptions: createTsCompilerOptions(config, dependencies, options),
},
}),
peerDepsExternal({
packageJsonPath: options.project,
}),
postcss({
inject: true,
extract: options.extractCss,
autoModules: true,
plugins: [autoprefixer],
use: {
less: {
javascriptEnabled: options.javascriptEnabled,
},
},
}),
(0, plugin_node_resolve_1.default)({
preferBuiltins: true,
extensions: fileExtensions,
}),
useSwc && (0, swc_plugin_1.swc)(),
useBabel &&
(0, plugin_babel_1.getBabelInputPlugin)({
// Lets `@nx/js/babel` preset know that we are packaging.
caller: {
// @ts-ignore
// Ignoring type checks for caller since we have custom attributes
isNxPackage: true,
// Always target esnext and let rollup handle cjs
supportsStaticESM: true,
isModern: true,
},
cwd: (0, path_1.join)(context.root, sourceRoot),
rootMode: 'upward',
babelrc: true,
extensions: fileExtensions,
babelHelpers: 'bundled',
skipPreflightCheck: true,
exclude: /node_modules/,
plugins: [
format === 'esm'
? undefined
: require.resolve('babel-plugin-transform-async-to-promises'),
].filter(Boolean),
}),
commonjs(),
(0, analyze_plugin_1.analyze)(),
];
let externalPackages = [
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.peerDependencies || {}),
]; // If external is set to none, include all dependencies and peerDependencies in externalPackages
if (options.external === 'all') {
externalPackages = externalPackages
.concat(dependencies.map((d) => d.name))
.concat(npmDeps);
}
else if (Array.isArray(options.external) && options.external.length > 0) {
externalPackages = externalPackages.concat(options.external);
}
externalPackages = [...new Set(externalPackages)];
const rollupConfig = {
input: options.outputFileName
? {
[(0, path_1.parse)(options.outputFileName).name]: options.main,
}
: options.main,
output: {
format,
dir: `${options.outputPath}`,
name: (0, devkit_1.names)(context.projectName).className,
entryFileNames: `[name].${format === 'esm' ? 'js' : 'cjs'}`,
chunkFileNames: `[name].${format === 'esm' ? 'js' : 'cjs'}`,
},
external: (id) => {
return externalPackages.some((name) => id === name || id.startsWith(`${name}/`)); // Could be a deep import
},
plugins,
};
return options.rollupConfig.reduce((currentConfig, plugin) => {
return require(plugin)(currentConfig, options);
}, rollupConfig);
});
}
exports.createRollupOptions = createRollupOptions;
function createTsCompilerOptions(config, dependencies, options) {
const compilerOptionPaths = (0, buildable_libs_utils_1.computeCompilerOptionsPaths)(config, dependencies);
const compilerOptions = {
rootDir: options.projectRoot,
allowJs: options.allowJs,
declaration: true,
paths: compilerOptionPaths,
};
if (config.options.module === ts.ModuleKind.CommonJS) {
compilerOptions['module'] = 'ESNext';
}
return compilerOptions;
}
function convertCopyAssetsToRollupOptions(outputPath, assets) {
return assets
? assets.map((a) => ({
src: (0, path_1.join)(a.input, a.glob).replace(/\\/g, '/'),
dest: (0, path_1.join)(outputPath, a.output).replace(/\\/g, '/'),
}))
: undefined;
}
function readCompatibleFormats(config) {
switch (config.options.module) {
case ts.ModuleKind.CommonJS:
case ts.ModuleKind.UMD:
case ts.ModuleKind.AMD:
return ['cjs'];
default:
return ['esm'];
}
}
function resolveOutfile(context, options) {
var _a, _b;
if (!((_a = options.format) === null || _a === void 0 ? void 0 : _a.includes('cjs')))
return undefined;
const { name } = (0, path_1.parse)((_b = options.outputFileName) !== null && _b !== void 0 ? _b : options.main);
return (0, path_1.resolve)(context.root, options.outputPath, `${name}.cjs`);
}
exports.default = (0, devkit_1.convertNxExecutor)(rollupExecutor);
//# sourceMappingURL=rollup.impl.js.map
{
"builders": "./builder.json"
}
{
"version": 2,
"outputCapture": "direct-nodejs",
"title": "Web Library Rollup Target (Experimental)",
"description": "Packages a library for different web usages (ESM, CommonJS).",
"cli": "nx",
"type": "object",
"presets": [
{
"name": "Including Dependencies in package.json",
"keys": [
"main",
"outputPath",
"project",
"tsConfig",
"buildableProjectDepsInPackageJsonType",
"updateBuildableProjectDepsInPackageJson"
]
}
],
"properties": {
"project": {
"type": "string",
"description": "The path to package.json file.",
"x-priority": "important"
},
"main": {
"type": "string",
"description": "The path to the entry file, relative to project.",
"alias": "entryFile",
"x-completion-type": "file",
"x-completion-glob": "**/*@(.js|.ts)",
"x-priority": "important"
},
"outputPath": {
"type": "string",
"description": "The output path of the generated files.",
"x-completion-type": "directory",
"x-priority": "important"
},
"outputFileName": {
"type": "string",
"description": "Name of the main output file. Defaults same basename as 'main' file."
},
"deleteOutputPath": {
"type": "boolean",
"description": "Delete the output path before building.",
"default": true
},
"tsConfig": {
"type": "string",
"description": "The path to tsconfig file.",
"x-completion-type": "file",
"x-completion-glob": "tsconfig.*.json",
"x-priority": "important"
},
"allowJs": {
"type": "boolean",
"description": "Allow JavaScript files to be compiled.",
"default": false
},
"format": {
"type": "array",
"description": "List of module formats to output. Defaults to matching format from tsconfig (e.g. CJS for CommonJS, and ESM otherwise).",
"alias": "f",
"items": {
"type": "string",
"enum": ["esm", "cjs"]
}
},
"external": {
"type": "array",
"description": "A list of external modules that will not be bundled (`react`, `react-dom`, etc.).",
"oneOf": [
{
"type": "string",
"enum": ["all", "none"]
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"watch": {
"type": "boolean",
"description": "Enable re-building when files change.",
"default": false
},
"updateBuildableProjectDepsInPackageJson": {
"type": "boolean",
"description": "Update buildable project dependencies in `package.json`.",
"default": true
},
"buildableProjectDepsInPackageJsonType": {
"type": "string",
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
},
"rollupConfig": {
"oneOf": [
{
"type": "array",
"items": {
"type": "string",
"x-completion-type": "file",
"x-completion-glob": "rollup?(*)@(.js|.ts)"
}
},
{
"type": "string",
"x-completion-type": "file",
"x-completion-glob": "rollup?(*)@(.js|.ts)"
}
],
"description": "Path to a function which takes a rollup config and returns an updated rollup config."
},
"extractCss": {
"type": ["boolean", "string"],
"description": "CSS files will be extracted to the output folder. Alternatively custom filename can be provided (e.g. styles.css)",
"default": true
},
"assets": {
"type": "array",
"description": "List of static assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"compiler": {
"type": "string",
"enum": ["babel", "swc", "tsc"],
"default": "babel",
"description": "Which compiler to use."
},
"javascriptEnabled": {
"type": "boolean",
"description": "Sets `javascriptEnabled` option for less loader",
"default": false
},
"generateExportsField": {
"type": "boolean",
"description": "Generate package.json with 'exports' field. This field defines entry points in the package and is used by Node and the TypeScript compiler.",
"default": false
},
"skipTypeCheck": {
"type": "boolean",
"description": "Whether to skip TypeScript type checking.",
"default": false
}
},
"required": ["tsConfig", "main", "outputPath"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply `glob`. Defaults to the project root."
},
"output": {
"type": "string",
"description": "Relative path within the output folder."
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
}
},
"examplesFile": "../../docs/rollup-examples.md"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment