Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow to opt-out of default babel plugins and presets #41

Closed
wants to merge 9 commits into from
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ In comparison with the [official plugin](https://github.com/rollup/rollup-plugin
- [Full list of plugin options](#full-list-of-plugin-options)
- [`transpiler`](#transpiler)
- [`babelConfig`](#babelconfig)
- [`noBabelConfigCustomization`](#nobabelconfigcustomization)
- [`tsconfig`](#tsconfig)
- [`browserslist`](#browserslist)
- [`cwd`](#cwd)
Expand Down Expand Up @@ -472,6 +473,12 @@ Type: `string | Partial<IBabelInputOptions>`

This option will only be respected when `"babel"` is being used as the `transpiler` and can be used to provide a [Babel config](https://babeljs.io/docs/en/options) or a path to one.

#### `noBabelConfigCustomization`

Type: `boolean`

If this option is `true`, **no** [default presets or plugins](#default-babel-plugins) are applied to the babel config. The [ignored/overridden Babel options](#ignoredoverridden-babel-options) are nevertheless applied.

#### `tsconfig`

Type: `string | Partial<CompilerOptions> | Partial<Record<keyof CompilerOptions, string | number | boolean>> | ParsedCommandLine | TsConfigResolver | TsConfigResolverWithFileName`
Expand Down Expand Up @@ -586,6 +593,8 @@ By default, the plugin will conditionally apply the `@babel/preset-env` preset i

If you provide these presets or plugins yourself through the found or provided Babel config, _your_ config options will take precedence.

In case that this is not enough for you setting [`noBabelConfigCustomization`](#nobabelconfigcustomization) disables these defaults.

Here's table with a full overview of the specifics:

| Preset/Plugin | Condition | Reason |
Expand Down
8 changes: 4 additions & 4 deletions src/constant/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ export const REGENERATOR_RUNTIME_NAME_2 = `${BABEL_RUNTIME_PREFIX_2}regenerator/
export const BABEL_REQUIRE_RUNTIME_HELPER_REGEXP_1 = new RegExp(`(require\\(["'\`])(${BABEL_RUNTIME_PREFIX_1}helpers/esm/[^"'\`]*)["'\`]\\)`);
export const BABEL_REQUIRE_RUNTIME_HELPER_REGEXP_2 = new RegExp(`(require\\(["'\`])(${BABEL_RUNTIME_PREFIX_2}helpers/esm/[^"'\`]*)["'\`]\\)`);

export const BABEL_MINIFICATION_BLACKLIST_PRESET_NAMES = [];
export const BABEL_CHUNK_BLACKLIST_PRESET_NAMES = [];

export const BABEL_MINIFICATION_BLACKLIST_PLUGIN_NAMES = ["@babel/plugin-transform-runtime", "babel-plugin-transform-runtime"];
export const BABEL_CHUNK_BLACKLIST_PLUGIN_NAMES = ["@babel/plugin-transform-runtime", "babel-plugin-transform-runtime"];

export const BABEL_MINIFY_PRESET_NAMES = ["babel-preset-minify"];
export const BABEL_CHUNK_PRESET_NAMES = ["babel-preset-minify"];

export const BABEL_MINIFY_PLUGIN_NAMES = [
export const BABEL_CHUNK_PLUGIN_NAMES = [
"babel-plugin-transform-minify-booleans",
"babel-plugin-minify-builtins",
"babel-plugin-transform-inline-consecutive-adds",
Expand Down
1 change: 1 addition & 0 deletions src/plugin/i-typescript-plugin-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface ITypescriptPluginTypescriptOptions extends ITypescriptPluginBas

export interface ITypescriptPluginBabelOptions extends ITypescriptPluginBaseOptions {
transpiler: "babel";
noBabelConfigCustomization?: boolean;
babelConfig?: string | Partial<IBabelInputOptions>;
}

Expand Down
13 changes: 7 additions & 6 deletions src/plugin/typescript-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ export default function typescriptRollupPlugin(pluginInputOptions: Partial<Types
/**
* If babel is to be used, and if one or more minify presets/plugins has been passed, this config will be used
*/
let babelMinifyConfig: ((filename: string) => IBabelConfig) | undefined;
let babelChunkConfig: ((filename: string) => IBabelConfig) | undefined;

/**
* If babel is to be used, and if one or more minify presets/plugins has been passed, this will be true
*/
let hasBabelMinifyOptions: boolean = false;
let hasBabelChunkOptions: boolean = false;

/**
* The (Incremental) LanguageServiceHost to use
Expand Down Expand Up @@ -176,14 +176,15 @@ export default function typescriptRollupPlugin(pluginInputOptions: Partial<Types
const babelConfigResult = getBabelConfig({
cwd,
babelConfig: pluginOptions.babelConfig,
noBabelConfigCustomization: pluginOptions.noBabelConfigCustomization,
forcedOptions: getForcedBabelOptions({cwd, pluginOptions, rollupInputOptions, browserslist: computedBrowserslist}),
defaultOptions: getDefaultBabelOptions({pluginOptions, rollupInputOptions, browserslist: computedBrowserslist}),
browserslist: computedBrowserslist,
rollupInputOptions
});
babelConfig = babelConfigResult.config;
babelMinifyConfig = babelConfigResult.minifyConfig;
hasBabelMinifyOptions = babelConfigResult.hasMinifyOptions;
babelChunkConfig = babelConfigResult.chunkConfig;
hasBabelChunkOptions = babelConfigResult.hasChunkOptions;
}

SUPPORTED_EXTENSIONS = getSupportedExtensions(
Expand Down Expand Up @@ -271,15 +272,15 @@ export default function typescriptRollupPlugin(pluginInputOptions: Partial<Types
}

// Don't proceed if there is no minification config
if (!hasBabelMinifyOptions || babelMinifyConfig == null) {
if (!hasBabelChunkOptions || babelChunkConfig == null) {
return updatedSourceDescription == null ? null : updatedSourceDescription;
}

const updatedCode = updatedSourceDescription != null ? updatedSourceDescription.code : code;
const updatedMap = updatedSourceDescription != null ? updatedSourceDescription.map : undefined;

const transpilationResult = await transformAsync(updatedCode, {
...babelMinifyConfig(chunk.fileName),
...babelChunkConfig(chunk.fileName),
filename: chunk.fileName,
filenameRelative: ensureRelative(cwd, chunk.fileName),
...(updatedMap == null
Expand Down
2 changes: 2 additions & 0 deletions src/util/get-babel-config/get-babel-config-options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {ITypescriptPluginBabelOptions} from "../../plugin/i-typescript-plugin-options";
import {IGetForcedBabelOptionsResult} from "../get-forced-babel-options/i-get-forced-babel-options-result";
import {IGetDefaultBabelOptionsResult} from "../get-default-babel-options/i-get-default-babel-options-result";
import {InputOptions} from "rollup";
Expand All @@ -8,4 +9,5 @@ export interface GetBabelConfigOptions extends FindBabelConfigOptions {
rollupInputOptions: InputOptions;
forcedOptions?: IGetForcedBabelOptionsResult;
defaultOptions?: IGetDefaultBabelOptionsResult;
noBabelConfigCustomization?: ITypescriptPluginBabelOptions["noBabelConfigCustomization"];
}
4 changes: 2 additions & 2 deletions src/util/get-babel-config/get-babel-config-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import {IBabelConfig} from "../../plugin/i-babel-options";

export interface GetBabelConfigResult {
config(filename: string): IBabelConfig;
minifyConfig: ((filename: string) => IBabelConfig) | undefined;
hasMinifyOptions: boolean;
chunkConfig: ((filename: string) => IBabelConfig) | undefined;
hasChunkOptions: boolean;
}
184 changes: 148 additions & 36 deletions src/util/get-babel-config/get-babel-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {dirname} from "path";
import {InputOptions} from "rollup";
import {IBabelConfigItem} from "../../plugin/i-babel-options";
import {isBabelPluginTransformRuntime, isBabelPresetEnv, isYearlyBabelPreset, nativeNormalize} from "../path/path-util";
import {
BABEL_MINIFICATION_BLACKLIST_PLUGIN_NAMES,
BABEL_MINIFICATION_BLACKLIST_PRESET_NAMES,
BABEL_MINIFY_PLUGIN_NAMES,
BABEL_MINIFY_PRESET_NAMES,
BABEL_CHUNK_BLACKLIST_PLUGIN_NAMES,
BABEL_CHUNK_BLACKLIST_PRESET_NAMES,
BABEL_CHUNK_PLUGIN_NAMES,
BABEL_CHUNK_PRESET_NAMES,
FORCED_BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS,
FORCED_BABEL_PRESET_ENV_OPTIONS,
FORCED_BABEL_YEARLY_PRESET_OPTIONS
Expand All @@ -17,17 +19,35 @@ import {findBabelConfig} from "./find-babel-config";

// tslint:disable:no-any

interface IBabelPlugin {
key: string;
options?: {[key: string]: unknown};
}

function getBabelItemId(item: IBabelConfigItem | IBabelPlugin): string {
if ("file" in item) return item.file.resolved;

const {key} = item;

// 1) full file path to plugin entrypoint
// 2 & 3) full module name
if (key.startsWith("/") || key.startsWith("@") || key.startsWith("babel-plugin-")) return key;

// add prefix to match keys defined in BABEL_CHUNK_PLUGIN_NAMES
return `babel-plugin-${key}`;
}

/**
* Combines the given two sets of presets
*/
function combineConfigItems(
userItems: IBabelConfigItem[],
defaultItems: IBabelConfigItem[] = [],
forcedItems: IBabelConfigItem[] = [],
useMinifyOptions: boolean = false
userItems: (IBabelConfigItem | IBabelPlugin)[],
defaultItems: (IBabelConfigItem | IBabelPlugin)[] = [],
forcedItems: (IBabelConfigItem | IBabelPlugin)[] = [],
useChunkOptions: boolean = false
): {}[] {
const namesInUserItems = new Set(userItems.map(item => item.file.resolved));
const namesInForcedItems = new Set(forcedItems.map(item => item.file.resolved));
const namesInUserItems = new Set(userItems.map(getBabelItemId));
const namesInForcedItems = new Set(forcedItems.map(getBabelItemId));
const userItemsHasYearlyPreset = [...namesInUserItems].some(isYearlyBabelPreset);

return (
Expand All @@ -36,48 +56,89 @@ function combineConfigItems(
// If the options contains a yearly preset such as "preset-es2015", filter out preset-env from the default items if it is given
...defaultItems.filter(
item =>
!namesInUserItems.has(item.file.resolved) &&
!namesInForcedItems.has(item.file.resolved) &&
(!userItemsHasYearlyPreset || !isBabelPresetEnv(item.file.resolved))
!namesInUserItems.has(getBabelItemId(item)) &&
!namesInForcedItems.has(getBabelItemId(item)) &&
(!userItemsHasYearlyPreset || !isBabelPresetEnv(getBabelItemId(item)))
),

// Only use those user items that doesn't appear within the forced items
...userItems.filter(item => !namesInForcedItems.has(item.file.resolved)),
...userItems.filter(item => !namesInForcedItems.has(getBabelItemId(item))),

// Apply the forced items at all times
...forcedItems
]
// Filter out those options that do not apply depending on whether or not to apply minification
// Filter out those options that do not apply depending chunk generation
.filter(configItem =>
useMinifyOptions ? configItemIsAllowedDuringMinification(configItem) : configItemIsAllowedDuringNoMinification(configItem)
useChunkOptions ? configItemIsAllowedForChunk(getBabelItemId(configItem)) : configItemIsAllowedForTransform(getBabelItemId(configItem))
)
);
}

/**
* Returns true if the given configItem is related to minification
*/
function configItemIsMinificationRelated({file: {resolved}}: IBabelConfigItem): boolean {
return BABEL_MINIFY_PRESET_NAMES.some(preset => resolved.includes(preset)) || BABEL_MINIFY_PLUGIN_NAMES.some(plugin => resolved.includes(plugin));
function configItemIsChunkRelated(item: string | IBabelConfigItem): boolean {
const id = typeof item === "string" ? item : getBabelItemId(item);

return (
(/\bminify\b/.test(id) ||
BABEL_CHUNK_PRESET_NAMES.some(preset => id.includes(preset)) ||
BABEL_CHUNK_PLUGIN_NAMES.some(plugin => id.includes(plugin))) &&
!(
BABEL_CHUNK_BLACKLIST_PLUGIN_NAMES.some(preset => id.includes(preset)) || BABEL_CHUNK_BLACKLIST_PRESET_NAMES.some(plugin => id.includes(plugin))
)
);
}

function configItemIsSyntaxRelated(id: string): boolean {
return /\bsyntax\b/.test(id);
}

/**
* Returns true if the given configItem is allowed during minification
*/
function configItemIsAllowedDuringMinification({file: {resolved}}: IBabelConfigItem): boolean {
return (
BABEL_MINIFICATION_BLACKLIST_PRESET_NAMES.every(preset => !resolved.includes(preset)) &&
BABEL_MINIFICATION_BLACKLIST_PLUGIN_NAMES.every(plugin => !resolved.includes(plugin))
);
function configItemIsAllowedForChunk(id: string): boolean {
return configItemIsSyntaxRelated(id) || configItemIsChunkRelated(id);
}

/**
* Returns true if the given configItem is allowed when not applying minification
*/
function configItemIsAllowedDuringNoMinification({file: {resolved}}: IBabelConfigItem): boolean {
return (
BABEL_MINIFY_PRESET_NAMES.every(preset => !resolved.includes(preset)) && BABEL_MINIFY_PLUGIN_NAMES.every(plugin => !resolved.includes(plugin))
);
function configItemIsAllowedForTransform(id: string): boolean {
return configItemIsSyntaxRelated(id) || !configItemIsChunkRelated(id);
}

type BabelTransformOptions = Partial<ReturnType<typeof FORCED_BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS>> & {corejs?: boolean; useESModules?: boolean};

function enforceBabelTransformRuntime<T extends IBabelConfigItem | IBabelPlugin>(
plugins: T[],
rollupInputOptions: InputOptions,
filename: string
): T[] {
const babelTransformPlugin = plugins.find(item => isBabelPluginTransformRuntime(getBabelItemId(item)));

const babelTransformOptions: BabelTransformOptions = {
...FORCED_BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS(rollupInputOptions),
corejs: false
};

if (babelTransformPlugin != null) {
const options: BabelTransformOptions = babelTransformPlugin.options != null ? babelTransformPlugin.options : {};

// set default options but keep explicitly defined options
babelTransformPlugin.options = {
...babelTransformOptions,
...options
};

return plugins;
}

return [
...plugins,
// Force the use of helpers (e.g. the runtime). But *don't* apply polyfills.
createConfigItem(["@babel/plugin-transform-runtime", babelTransformOptions], {type: "plugin", dirname: dirname(filename)})
];
}

/**
Expand All @@ -86,12 +147,65 @@ function configItemIsAllowedDuringNoMinification({file: {resolved}}: IBabelConfi
export function getBabelConfig({
babelConfig,
cwd,
noBabelConfigCustomization,
forcedOptions = {},
defaultOptions = {},
browserslist
}: GetBabelConfigOptions): GetBabelConfigResult {
const resolvedConfig = findBabelConfig({cwd, babelConfig});

if (noBabelConfigCustomization === true) {
const loadOptionsForFilename = (filename: string, useChunkOptions: boolean = false) => {
const partialCustomConfig =
resolvedConfig != null && resolvedConfig.kind === "dict"
? // If the given babelConfig is an object of input options, use that as the basis for the full config
resolvedConfig
: // Load the path to a babel config provided to the plugin if any, otherwise try to resolve it
loadPartialConfig({
cwd,
root: cwd,
...(resolvedConfig != null ? {configFile: resolvedConfig.path} : {babelrc: true}),
filename
});

// fully load all options, which results in a flat plugins structure
// which can then be used to match chunk plugins
const fullOptions = loadOptions({
...partialCustomConfig.options,
...forcedOptions,
presets: partialCustomConfig.options.presets,
plugins: partialCustomConfig.options.plugins,
filename,
caller: {
name: "rollup-plugin-ts",
...(partialCustomConfig.options.caller ? partialCustomConfig.options.caller : {}),
supportsStaticESM: true,
supportsDynamicImport: true
}
});

// sourceMap is an alias for 'sourceMaps'. If the user provided it, make sure it is undefined. Otherwise, Babel will fail during validation
if ("sourceMap" in fullOptions) {
delete fullOptions.sourceMap;
}

// Force the use of helpers (e.g. the runtime) for transform
const plugins = useChunkOptions ? fullOptions.plugins : enforceBabelTransformRuntime(fullOptions.plugins, rollupInputOptions, filename);

return {
...fullOptions,
// presets is an empty array as babel as resolved all plugins
plugins: combineConfigItems(plugins, [], [], useChunkOptions)
};
};

return {
config: filename => loadOptionsForFilename(filename, false),
chunkConfig: filename => loadOptionsForFilename(filename, true),
hasChunkOptions: true
};
}

// Load a partial Babel config based on the input options
const partialConfig = loadPartialConfig(
resolvedConfig != null && resolvedConfig.kind === "dict"
Expand Down Expand Up @@ -199,8 +313,8 @@ export function getBabelConfig({
delete combined.sourceMap;
}

// Combine the partial config with the default and forced options for the minifyConfig
const minifyCombined = {
// Combine the partial config with the default and forced options for the chunkConfig
const chunkCombined = {
...combined,
presets: combineConfigItems(
options.presets,
Expand All @@ -217,18 +331,16 @@ export function getBabelConfig({
};

const finalConfigFileOption = config == null ? configFileOption : {configFile: config};
const finalMinifyConfigFileOption = config == null ? configFileOption : {configFile: `${config}.minify`};
const finalchunkConfigFileOption = config == null ? configFileOption : {configFile: `${config}.minify`};

// Normalize the options
return {
config: filename => loadOptions({...combined, filename, ...finalConfigFileOption}),
// Only return the minify config if it includes at least one plugin or preset
minifyConfig:
minifyCombined.plugins.length < 1 && minifyCombined.presets.length < 1
chunkConfig:
chunkCombined.plugins.length < 1 && chunkCombined.presets.length < 1
? undefined
: filename => loadOptions({...minifyCombined, filename, ...finalMinifyConfigFileOption}),
hasMinifyOptions:
[...minifyCombined.plugins.filter(configItemIsMinificationRelated), ...minifyCombined.presets.filter(configItemIsMinificationRelated)].length >
0
: filename => loadOptions({...chunkCombined, filename, ...finalchunkConfigFileOption}),
hasChunkOptions: [...chunkCombined.plugins.filter(configItemIsChunkRelated), ...chunkCombined.presets.filter(configItemIsChunkRelated)].length > 0
};
}
Loading