[babel] ensure TS preset runs before anything else (#119107)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Spencer 2021-11-29 10:29:45 -08:00 committed by GitHub
parent c6db25a3ef
commit b2f54829d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 62 deletions

View file

@ -6,46 +6,57 @@
* Side Public License, v 1.
*/
const plugins = [
require.resolve('babel-plugin-add-module-exports'),
// The class properties proposal was merged with the private fields proposal
// into the "class fields" proposal. Babel doesn't support this combined
// proposal yet, which includes private field, so this transform is
// TECHNICALLY stage 2, but for all intents and purposes it's stage 3
//
// See https://github.com/babel/proposals/issues/12 for progress
require.resolve('@babel/plugin-proposal-class-properties'),
// Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining)
// Need this since we are using TypeScript 3.7+
require.resolve('@babel/plugin-proposal-optional-chaining'),
// Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing)
// Need this since we are using TypeScript 3.7+
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
// Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from)
// Need this since we are using TypeScript 3.8+
require.resolve('@babel/plugin-proposal-export-namespace-from'),
// Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from)
// Need this since we are using TypeScript 3.9+
require.resolve('@babel/plugin-proposal-private-methods'),
// It enables the @babel/runtime so we can decrease the bundle sizes of the produced outputs
[
require.resolve('@babel/plugin-transform-runtime'),
{
version: '^7.12.5',
},
],
];
module.exports = {
presets: [
[require.resolve('@babel/preset-typescript'), { allowNamespaces: true }],
// plugins always run before presets, but in this case we need the
// @babel/preset-typescript preset to run first so we have to move
// our explicit plugin configs to a sub-preset
{
plugins: [
require.resolve('babel-plugin-add-module-exports'),
// The class properties proposal was merged with the private fields proposal
// into the "class fields" proposal. Babel doesn't support this combined
// proposal yet, which includes private field, so this transform is
// TECHNICALLY stage 2, but for all intents and purposes it's stage 3
//
// See https://github.com/babel/proposals/issues/12 for progress
require.resolve('@babel/plugin-proposal-class-properties'),
// Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining)
// Need this since we are using TypeScript 3.7+
require.resolve('@babel/plugin-proposal-optional-chaining'),
// Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing)
// Need this since we are using TypeScript 3.7+
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
// Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from)
// Need this since we are using TypeScript 3.8+
require.resolve('@babel/plugin-proposal-export-namespace-from'),
// Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from)
// Need this since we are using TypeScript 3.9+
require.resolve('@babel/plugin-proposal-private-methods'),
// It enables the @babel/runtime so we can decrease the bundle sizes of the produced outputs
[
require.resolve('@babel/plugin-transform-runtime'),
{
version: '^7.12.5',
},
],
],
},
require.resolve('@babel/preset-react'),
[
require.resolve('@babel/preset-typescript'),
{
allowNamespaces: true,
allowDeclareFields: true,
},
],
],
plugins,
};

View file

@ -35,14 +35,14 @@ export async function runFindBabelHelpersInEntryBundlesCli() {
}
for (const { userRequest } of module.reasons) {
if (userRequest.startsWith('@babel/runtime/')) {
if (userRequest.startsWith('@babel/runtime')) {
imports.add(userRequest);
}
}
}
}
log.success('found', imports.size, '@babel/register imports in entry bundles');
log.success('found', imports.size, '@babel/runtime* imports in entry bundles');
log.write(
Array.from(imports, (i) => `'${i}',`)
.sort()

View file

@ -26,7 +26,7 @@ export class EmitStatsPlugin {
(stats) => {
Fs.writeFileSync(
Path.resolve(this.bundle.outputDir, 'stats.json'),
JSON.stringify(stats.toJson())
JSON.stringify(stats.toJson(), null, 2)
);
}
);

View file

@ -9,11 +9,13 @@
import { externals } from '@kbn/ui-shared-deps-src';
import { stringifyRequest } from 'loader-utils';
import { resolve } from 'path';
import { Configuration, Stats } from 'webpack';
import webpack, { Configuration, Stats } from 'webpack';
import webpackMerge from 'webpack-merge';
import { REPO_ROOT } from './lib/constants';
import { IgnoreNotFoundExportPlugin } from './ignore_not_found_export_plugin';
type Preset = string | [string, Record<string, unknown>] | Record<string, unknown>;
const stats = {
...Stats.presetToOptions('minimal'),
colors: true,
@ -22,6 +24,46 @@ const stats = {
moduleTrace: true,
};
function isProgressPlugin(plugin: any) {
return 'handler' in plugin && plugin.showActiveModules && plugin.showModules;
}
function isHtmlPlugin(plugin: any): plugin is { options: { template: string } } {
return !!(typeof plugin.options?.template === 'string');
}
function isBabelLoaderRule(rule: webpack.RuleSetRule): rule is webpack.RuleSetRule & {
use: webpack.RuleSetLoader[];
} {
return !!(
rule.use &&
Array.isArray(rule.use) &&
rule.use.some(
(l) =>
typeof l === 'object' && typeof l.loader === 'string' && l.loader.includes('babel-loader')
)
);
}
function getPresetPath(preset: Preset) {
if (typeof preset === 'string') return preset;
if (Array.isArray(preset)) return preset[0];
return undefined;
}
function getTsPreset(preset: Preset) {
if (getPresetPath(preset)?.includes('preset-typescript')) {
if (typeof preset === 'string') return [preset, {}];
if (Array.isArray(preset)) return preset;
throw new Error('unsupported preset-typescript format');
}
}
function isDesiredPreset(preset: Preset) {
return !getPresetPath(preset)?.includes('preset-flow');
}
// Extend the Storybook Webpack config with some customizations
/* eslint-disable import/no-default-export */
export default function ({ config: storybookConfig }: { config: Configuration }) {
@ -83,21 +125,72 @@ export default function ({ config: storybookConfig }: { config: Configuration })
stats,
};
// Disable the progress plugin
const progressPlugin: any = (storybookConfig.plugins || []).find((plugin: any) => {
return 'handler' in plugin && plugin.showActiveModules && plugin.showModules;
});
progressPlugin.handler = () => {};
const updatedModuleRules = [];
// clone and modify the module.rules config provided by storybook so that the default babel plugins run after the typescript preset
for (const originalRule of storybookConfig.module?.rules ?? []) {
const rule = { ...originalRule };
updatedModuleRules.push(rule);
// This is the hacky part. We find something that looks like the
// HtmlWebpackPlugin and mutate its `options.template` to point at our
// revised template.
const htmlWebpackPlugin: any = (storybookConfig.plugins || []).find((plugin: any) => {
return plugin.options && typeof plugin.options.template === 'string';
});
if (htmlWebpackPlugin) {
htmlWebpackPlugin.options.template = require.resolve('../templates/index.ejs');
if (isBabelLoaderRule(rule)) {
rule.use = [...rule.use];
const loader = (rule.use[0] = { ...rule.use[0] });
const options = (loader.options = { ...(loader.options as Record<string, any>) });
// capture the plugins defined at the root level
const plugins: string[] = options.plugins;
options.plugins = [];
// move the plugins to the top of the preset array so they will run after the typescript preset
options.presets = [
{
plugins,
},
...(options.presets as Preset[]).filter(isDesiredPreset).map((preset) => {
const tsPreset = getTsPreset(preset);
if (!tsPreset) {
return preset;
}
return [
tsPreset[0],
{
...tsPreset[1],
allowNamespaces: true,
allowDeclareFields: true,
},
];
}),
];
}
}
return webpackMerge(storybookConfig, config);
// copy and modify the webpack plugins added by storybook
const filteredStorybookPlugins = [];
for (const plugin of storybookConfig.plugins ?? []) {
// Remove the progress plugin
if (isProgressPlugin(plugin)) {
continue;
}
// This is the hacky part. We find something that looks like the
// HtmlWebpackPlugin and mutate its `options.template` to point at our
// revised template.
if (isHtmlPlugin(plugin)) {
plugin.options.template = require.resolve('../templates/index.ejs');
}
filteredStorybookPlugins.push(plugin);
}
return webpackMerge(
{
...storybookConfig,
plugins: filteredStorybookPlugins,
module: {
...storybookConfig.module,
rules: updatedModuleRules,
},
},
config
);
}

View file

@ -14,9 +14,11 @@ const MAX_NUMBER_OF_HISTORY_ITEMS = 100;
export const isQuotaExceededError = (e: Error): boolean => e.name === 'QuotaExceededError';
export class History {
constructor(private readonly storage: Storage) {}
private changeEmitter: BehaviorSubject<any[]>;
private changeEmitter = new BehaviorSubject<any[]>(this.getHistory() || []);
constructor(private readonly storage: Storage) {
this.changeEmitter = new BehaviorSubject(this.getHistory() || []);
}
getHistoryKeys() {
return this.storage

View file

@ -32,7 +32,7 @@ interface State {
export class DashboardViewport extends React.Component<DashboardViewportProps, State> {
static contextType = context;
public readonly context!: DashboardReactContextValue;
public declare readonly context: DashboardReactContextValue;
private controlsRoot: React.RefObject<HTMLDivElement>;

View file

@ -104,7 +104,7 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
<div>
Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
</div>
`);
@ -129,7 +129,7 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
<div>
Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}}}
Role Edit Page: {"action":"edit","roleName":"role@name","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/role@name","search":"","hash":""}}}
</div>
`);
@ -154,7 +154,7 @@ describe('rolesManagementApp', () => {
expect(docTitle.reset).not.toHaveBeenCalled();
expect(container).toMatchInlineSnapshot(`
<div>
Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"fieldCache":{}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"indicesAPIClient":{"fieldCache":{},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
</div>
`);