Skip to content

Commit

Permalink
[babel] ensure TS preset runs before anything else (elastic#119107) (e…
Browse files Browse the repository at this point in the history
…lastic#119907)

Co-authored-by: Kibana Machine <[email protected]>
# Conflicts:
#	packages/kbn-storybook/src/webpack.config.ts
#	src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx
  • Loading branch information
Spencer authored Nov 29, 2021
1 parent edffd87 commit 6d24ae4
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 65 deletions.
85 changes: 48 additions & 37 deletions packages/kbn-babel-preset/common_preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
module.exports = {
presets: [
// 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
{
version: '^7.12.5',
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 }],
require.resolve('@babel/preset-react'),

[
require.resolve('@babel/preset-typescript'),
{
allowNamespaces: true,
allowDeclareFields: true,
},
],
],
plugins,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/src/worker/emit_stats_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
);
Expand Down
128 changes: 109 additions & 19 deletions packages/kbn-storybook/src/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 }) {
Expand Down Expand Up @@ -83,24 +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 = () => {};

// 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');
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);

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,
},
];
}),
];
}
}

// @ts-ignore There's a long error here about the types of the
// incompatibility of Configuration, but it looks like it just may be Webpack
// type definition related.
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
);
}
6 changes: 4 additions & 2 deletions src/plugins/console/public/services/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ interface State {
export class DashboardViewport extends React.Component<DashboardViewportProps, State> {
static contextType = context;

public readonly context!: DashboardReactContextValue;
public declare readonly context: DashboardReactContextValue;

private subscription?: Subscription;
private mounted: boolean = false;
constructor(props: DashboardViewportProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,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>
`);

Expand All @@ -128,7 +128,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>
`);

Expand All @@ -153,7 +153,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>
`);

Expand Down

0 comments on commit 6d24ae4

Please sign in to comment.