diff --git a/README.md b/README.md index 84a040d0..12750156 100644 --- a/README.md +++ b/README.md @@ -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) @@ -472,6 +473,12 @@ Type: `string | Partial` 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 | Partial> | ParsedCommandLine | TsConfigResolver | TsConfigResolverWithFileName` @@ -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 | diff --git a/src/constant/constant.ts b/src/constant/constant.ts index d35ba3c9..8f5aaab0 100644 --- a/src/constant/constant.ts +++ b/src/constant/constant.ts @@ -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", diff --git a/src/plugin/i-typescript-plugin-options.ts b/src/plugin/i-typescript-plugin-options.ts index 613260f8..af06076d 100644 --- a/src/plugin/i-typescript-plugin-options.ts +++ b/src/plugin/i-typescript-plugin-options.ts @@ -88,6 +88,7 @@ export interface ITypescriptPluginTypescriptOptions extends ITypescriptPluginBas export interface ITypescriptPluginBabelOptions extends ITypescriptPluginBaseOptions { transpiler: "babel"; + noBabelConfigCustomization?: boolean; babelConfig?: string | Partial; } diff --git a/src/plugin/typescript-plugin.ts b/src/plugin/typescript-plugin.ts index cb1aba67..1e3642b0 100644 --- a/src/plugin/typescript-plugin.ts +++ b/src/plugin/typescript-plugin.ts @@ -64,12 +64,12 @@ export default function typescriptRollupPlugin(pluginInputOptions: Partial 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 @@ -176,14 +176,15 @@ export default function typescriptRollupPlugin(pluginInputOptions: Partial IBabelConfig) | undefined; - hasMinifyOptions: boolean; + chunkConfig: ((filename: string) => IBabelConfig) | undefined; + hasChunkOptions: boolean; } diff --git a/src/util/get-babel-config/get-babel-config.ts b/src/util/get-babel-config/get-babel-config.ts index e71ce8c5..e2fb946c 100644 --- a/src/util/get-babel-config/get-babel-config.ts +++ b/src/util/get-babel-config/get-babel-config.ts @@ -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 @@ -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 ( @@ -36,20 +56,20 @@ 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)) ) ); } @@ -57,27 +77,68 @@ function combineConfigItems( /** * 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> & {corejs?: boolean; useESModules?: boolean}; + +function enforceBabelTransformRuntime( + 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)}) + ]; } /** @@ -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" @@ -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, @@ -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 }; } diff --git a/src/util/plugin-options/get-plugin-options.ts b/src/util/plugin-options/get-plugin-options.ts index c9a0925d..4b8878a8 100644 --- a/src/util/plugin-options/get-plugin-options.ts +++ b/src/util/plugin-options/get-plugin-options.ts @@ -48,6 +48,7 @@ export function getPluginOptions(options: Partial): Typ return { ...baseOptions, ...("babelConfig" in options ? {babelConfig: options.babelConfig} : {}), + ...("noBabelConfigCustomization" in options ? {noBabelConfigCustomization: options.noBabelConfigCustomization} : {}), transpiler: "babel" }; }