Commit 7985aa1e by kay delaney Committed by GitHub

Performance/Webpack: Introduces more aggressive code-splitting and other perf improvements (#18544)

* Performance/Webpack: Introduces more aggressive code-splitting and other perf improvements
- Introduces dynamic imports for built-in plugins
- Uses dynamic imports for various packages (rst2html, brace)
- Introduces route-based dynamic imports
- Splits angular and moment into separate bundles
parent 409874b3
...@@ -3,8 +3,11 @@ ...@@ -3,8 +3,11 @@
[ [
"@babel/preset-env", "@babel/preset-env",
{ {
"targets": { "browsers": "last 3 versions" }, "targets": {
"useBuiltIns": "entry" "browsers": "last 3 versions"
},
"useBuiltIns": "entry",
"modules": "false",
} }
] ]
] ]
......
...@@ -94,3 +94,6 @@ theOutput/ ...@@ -94,3 +94,6 @@ theOutput/
# Ignore go local build dependencies # Ignore go local build dependencies
/scripts/go/bin/** /scripts/go/bin/**
# Ignore compilation stats from `yarn stats`
compilation-stats.json
...@@ -147,6 +147,7 @@ ...@@ -147,6 +147,7 @@
"start:hot": "grafana-toolkit core:start --hot --watchTheme", "start:hot": "grafana-toolkit core:start --hot --watchTheme",
"start:ignoreTheme": "grafana-toolkit core:start --hot", "start:ignoreTheme": "grafana-toolkit core:start --hot",
"start:noTsCheck": "grafana-toolkit core:start --noTsCheck", "start:noTsCheck": "grafana-toolkit core:start --noTsCheck",
"stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json",
"watch": "yarn start -d watch,start core:start --watchTheme ", "watch": "yarn start -d watch,start core:start --watchTheme ",
"build": "grunt build", "build": "grunt build",
"test": "grunt test", "test": "grunt test",
......
import difference from 'lodash/difference';
import { fieldReducers, ReducerID, reduceField } from './fieldReducer'; import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
import _ from 'lodash';
import { Field, FieldType } from '../types/index'; import { Field, FieldType } from '../types/index';
import { MutableDataFrame } from './dataFrameHelper'; import { MutableDataFrame } from './dataFrameHelper';
import { ArrayVector } from './vector'; import { ArrayVector } from './vector';
...@@ -42,7 +43,7 @@ describe('Stats Calculators', () => { ...@@ -42,7 +43,7 @@ describe('Stats Calculators', () => {
expect(stats.length).toBe(2); expect(stats.length).toBe(2);
const found = stats.map(v => v.id); const found = stats.map(v => v.id);
const notFound = _.difference(names, found); const notFound = difference(names, found);
expect(notFound.length).toBe(2); expect(notFound.length).toBe(2);
expect(notFound[0]).toBe('not a stat'); expect(notFound[0]).toBe('not a stat');
......
// @ts-ignore import each from 'lodash/each';
import _ from 'lodash'; import groupBy from 'lodash/groupBy';
import { RawTimeRange } from '../types/time'; import { RawTimeRange } from '../types/time';
...@@ -64,12 +64,12 @@ const rangeOptions = [ ...@@ -64,12 +64,12 @@ const rangeOptions = [
const absoluteFormat = 'YYYY-MM-DD HH:mm:ss'; const absoluteFormat = 'YYYY-MM-DD HH:mm:ss';
const rangeIndex: any = {}; const rangeIndex: any = {};
_.each(rangeOptions, (frame: any) => { each(rangeOptions, (frame: any) => {
rangeIndex[frame.from + ' to ' + frame.to] = frame; rangeIndex[frame.from + ' to ' + frame.to] = frame;
}); });
export function getRelativeTimesList(timepickerSettings: any, currentDisplay: any) { export function getRelativeTimesList(timepickerSettings: any, currentDisplay: any) {
const groups = _.groupBy(rangeOptions, (option: any) => { const groups = groupBy(rangeOptions, (option: any) => {
option.active = option.display === currentDisplay; option.active = option.display === currentDisplay;
return option.section; return option.section;
}); });
......
...@@ -183,7 +183,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => { ...@@ -183,7 +183,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
{ {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: ['@babel/preset-env'], presets: ['@babel/preset-env', { modules: false }],
plugins: ['angularjs-annotate'], plugins: ['angularjs-annotate'],
}, },
}, },
......
// Libraries // Libraries
import _ from 'lodash';
import React from 'react'; import React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { GraphSeriesValue, AbsoluteTimeRange } from '@grafana/data'; import { GraphSeriesValue, AbsoluteTimeRange } from '@grafana/data';
......
...@@ -16,22 +16,22 @@ export const Footer: FC<Props> = React.memo( ...@@ -16,22 +16,22 @@ export const Footer: FC<Props> = React.memo(
<div className="text-center"> <div className="text-center">
<ul> <ul>
<li> <li>
<a href="http://docs.grafana.org" target="_blank"> <a href="http://docs.grafana.org" target="_blank" rel="noopener">
<i className="fa fa-file-code-o" /> Docs <i className="fa fa-file-code-o" /> Docs
</a> </a>
</li> </li>
<li> <li>
<a href="https://grafana.com/services/support" target="_blank"> <a href="https://grafana.com/services/support" target="_blank" rel="noopener">
<i className="fa fa-support" /> Support Plans <i className="fa fa-support" /> Support Plans
</a> </a>
</li> </li>
<li> <li>
<a href="https://community.grafana.com/" target="_blank"> <a href="https://community.grafana.com/" target="_blank" rel="noopener">
<i className="fa fa-comments-o" /> Community <i className="fa fa-comments-o" /> Community
</a> </a>
</li> </li>
<li> <li>
<a href="https://grafana.com" target="_blank"> <a href="https://grafana.com" target="_blank" rel="noopener">
{appName} {appName}
</a>{' '} </a>{' '}
<span> <span>
...@@ -41,7 +41,7 @@ export const Footer: FC<Props> = React.memo( ...@@ -41,7 +41,7 @@ export const Footer: FC<Props> = React.memo(
{newGrafanaVersionExists && ( {newGrafanaVersionExists && (
<li> <li>
<Tooltip placement="auto" content={newGrafanaVersion}> <Tooltip placement="auto" content={newGrafanaVersion}>
<a href="https://grafana.com/get" target="_blank"> <a href="https://grafana.com/get" target="_blank" rel="noopener">
New version available! New version available!
</a> </a>
</Tooltip> </Tooltip>
......
declare module 'brace/*' {
let brace: any;
export default brace;
}
...@@ -30,20 +30,6 @@ ...@@ -30,20 +30,6 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import config from 'app/core/config'; import config from 'app/core/config';
import ace from 'brace';
import './theme-grafana-dark';
import 'brace/ext/language_tools';
import 'brace/theme/textmate';
import 'brace/mode/text';
import 'brace/snippets/text';
import 'brace/mode/sql';
import 'brace/snippets/sql';
import 'brace/mode/sqlserver';
import 'brace/snippets/sqlserver';
import 'brace/mode/markdown';
import 'brace/snippets/markdown';
import 'brace/mode/json';
import 'brace/snippets/json';
const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark'; const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark';
const DEFAULT_THEME_LIGHT = 'ace/theme/textmate'; const DEFAULT_THEME_LIGHT = 'ace/theme/textmate';
...@@ -55,7 +41,7 @@ const DEFAULT_SNIPPETS = true; ...@@ -55,7 +41,7 @@ const DEFAULT_SNIPPETS = true;
const editorTemplate = `<div></div>`; const editorTemplate = `<div></div>`;
function link(scope: any, elem: any, attrs: any) { async function link(scope: any, elem: any, attrs: any) {
// Options // Options
const langMode = attrs.mode || DEFAULT_MODE; const langMode = attrs.mode || DEFAULT_MODE;
const maxLines = attrs.maxLines || DEFAULT_MAX_LINES; const maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
...@@ -66,6 +52,23 @@ function link(scope: any, elem: any, attrs: any) { ...@@ -66,6 +52,23 @@ function link(scope: any, elem: any, attrs: any) {
// Initialize editor // Initialize editor
const aceElem = elem.get(0); const aceElem = elem.get(0);
const { default: ace } = await import(/* webpackChunkName: "brace" */ 'brace');
await import('brace/ext/language_tools');
await import('brace/theme/textmate');
await import('brace/mode/text');
await import('brace/snippets/text');
await import('brace/mode/sql');
await import('brace/snippets/sql');
await import('brace/mode/sqlserver');
await import('brace/snippets/sqlserver');
await import('brace/mode/markdown');
await import('brace/snippets/markdown');
await import('brace/mode/json');
await import('brace/snippets/json');
// @ts-ignore
await import('./theme-grafana-dark');
const codeEditor = ace.edit(aceElem); const codeEditor = ace.edit(aceElem);
const editorSession = codeEditor.getSession(); const editorSession = codeEditor.getSession();
......
...@@ -160,6 +160,7 @@ exports[`ServerStats Should render table with stats 1`] = ` ...@@ -160,6 +160,7 @@ exports[`ServerStats Should render table with stats 1`] = `
<li> <li>
<a <a
href="http://docs.grafana.org" href="http://docs.grafana.org"
rel="noopener"
target="_blank" target="_blank"
> >
<i <i
...@@ -171,6 +172,7 @@ exports[`ServerStats Should render table with stats 1`] = ` ...@@ -171,6 +172,7 @@ exports[`ServerStats Should render table with stats 1`] = `
<li> <li>
<a <a
href="https://grafana.com/services/support" href="https://grafana.com/services/support"
rel="noopener"
target="_blank" target="_blank"
> >
<i <i
...@@ -182,6 +184,7 @@ exports[`ServerStats Should render table with stats 1`] = ` ...@@ -182,6 +184,7 @@ exports[`ServerStats Should render table with stats 1`] = `
<li> <li>
<a <a
href="https://community.grafana.com/" href="https://community.grafana.com/"
rel="noopener"
target="_blank" target="_blank"
> >
<i <i
...@@ -193,6 +196,7 @@ exports[`ServerStats Should render table with stats 1`] = ` ...@@ -193,6 +196,7 @@ exports[`ServerStats Should render table with stats 1`] = `
<li> <li>
<a <a
href="https://grafana.com" href="https://grafana.com"
rel="noopener"
target="_blank" target="_blank"
> >
Grafana Grafana
......
...@@ -131,6 +131,7 @@ class NewDataSourcePage extends PureComponent<Props> { ...@@ -131,6 +131,7 @@ class NewDataSourcePage extends PureComponent<Props> {
className="btn btn-inverse" className="btn btn-inverse"
href="https://grafana.com/plugins?type=datasource&utm_source=new-data-source" href="https://grafana.com/plugins?type=datasource&utm_source=new-data-source"
target="_blank" target="_blank"
rel="noopener"
> >
Find more data source plugins on grafana.com Find more data source plugins on grafana.com
</a> </a>
...@@ -198,6 +199,7 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => { ...@@ -198,6 +199,7 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
className="btn btn-inverse" className="btn btn-inverse"
href={`${learnMoreLink}?utm_source=grafana_add_ds`} href={`${learnMoreLink}?utm_source=grafana_add_ds`}
target="_blank" target="_blank"
rel="noopener"
onClick={onLearnMoreClick} onClick={onLearnMoreClick}
> >
Learn more <i className="fa fa-external-link add-datasource-item-actions__btn-icon" /> Learn more <i className="fa fa-external-link add-datasource-item-actions__btn-icon" />
......
...@@ -14,6 +14,7 @@ export const NoDataSourceCallToAction = () => { ...@@ -14,6 +14,7 @@ export const NoDataSourceCallToAction = () => {
<a <a
href="http://docs.grafana.org/administration/provisioning/#datasources?utm_source=explore" href="http://docs.grafana.org/administration/provisioning/#datasources?utm_source=explore"
target="_blank" target="_blank"
rel="noopener"
className="text-link" className="text-link"
> >
Learn more Learn more
......
// Libraries // Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import _ from 'lodash'; import debounce from 'lodash/debounce';
import has from 'lodash/has';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
// @ts-ignore // @ts-ignore
import { connect } from 'react-redux'; import { connect } from 'react-redux';
...@@ -97,7 +98,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> { ...@@ -97,7 +98,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
this.setState({ textEditModeEnabled: !this.state.textEditModeEnabled }); this.setState({ textEditModeEnabled: !this.state.textEditModeEnabled });
}; };
updateLogsHighlights = _.debounce((value: DataQuery) => { updateLogsHighlights = debounce((value: DataQuery) => {
const { datasourceInstance } = this.props; const { datasourceInstance } = this.props;
if (datasourceInstance.getHighlighterExpression) { if (datasourceInstance.getHighlighterExpression) {
const { exploreId } = this.props; const { exploreId } = this.props;
...@@ -120,7 +121,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> { ...@@ -120,7 +121,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
mode, mode,
} = this.props; } = this.props;
const canToggleEditorModes = const canToggleEditorModes =
mode === ExploreMode.Metrics && _.has(datasourceInstance, 'components.QueryCtrl.prototype.toggleEditorMode'); mode === ExploreMode.Metrics && has(datasourceInstance, 'components.QueryCtrl.prototype.toggleEditorMode');
const queryErrors = queryResponse.error && queryResponse.error.refId === query.refId ? [queryResponse.error] : []; const queryErrors = queryResponse.error && queryResponse.error.refId === query.refId ? [queryResponse.error] : [];
let QueryField; let QueryField;
......
...@@ -278,7 +278,7 @@ class PluginPage extends PureComponent<Props, State> { ...@@ -278,7 +278,7 @@ class PluginPage extends PureComponent<Props, State> {
{info.links.map(link => { {info.links.map(link => {
return ( return (
<li key={link.url}> <li key={link.url}>
<a href={link.url} className="external-link" target="_blank"> <a href={link.url} className="external-link" target="_blank" rel="noopener">
{link.name} {link.name}
</a> </a>
</li> </li>
......
import * as graphitePlugin from 'app/plugins/datasource/graphite/module'; const graphitePlugin = async () =>
import * as cloudwatchPlugin from 'app/plugins/datasource/cloudwatch/module'; await import(/* webpackChunkName: "graphitePlugin" */ 'app/plugins/datasource/graphite/module');
import * as dashboardDSPlugin from 'app/plugins/datasource/dashboard/module'; const cloudwatchPlugin = async () =>
import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/module'; await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module');
import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module'; const dashboardDSPlugin = async () =>
import * as grafanaPlugin from 'app/plugins/datasource/grafana/module'; await import(/* webpackChunkName "dashboardDSPlugin" */ 'app/plugins/datasource/dashboard/module');
import * as influxdbPlugin from 'app/plugins/datasource/influxdb/module'; const elasticsearchPlugin = async () =>
import * as lokiPlugin from 'app/plugins/datasource/loki/module'; await import(/* webpackChunkName: "elasticsearchPlugin" */ 'app/plugins/datasource/elasticsearch/module');
import * as mixedPlugin from 'app/plugins/datasource/mixed/module'; const opentsdbPlugin = async () =>
import * as mysqlPlugin from 'app/plugins/datasource/mysql/module'; await import(/* webpackChunkName: "opentsdbPlugin" */ 'app/plugins/datasource/opentsdb/module');
import * as postgresPlugin from 'app/plugins/datasource/postgres/module'; const grafanaPlugin = async () =>
import * as prometheusPlugin from 'app/plugins/datasource/prometheus/module'; await import(/* webpackChunkName: "grafanaPlugin" */ 'app/plugins/datasource/grafana/module');
import * as mssqlPlugin from 'app/plugins/datasource/mssql/module'; const influxdbPlugin = async () =>
import * as testDataDSPlugin from 'app/plugins/datasource/testdata/module'; await import(/* webpackChunkName: "influxdbPlugin" */ 'app/plugins/datasource/influxdb/module');
import * as inputDatasourcePlugin from 'app/plugins/datasource/input/module'; const lokiPlugin = async () => await import(/* webpackChunkName: "lokiPlugin" */ 'app/plugins/datasource/loki/module');
import * as stackdriverPlugin from 'app/plugins/datasource/stackdriver/module'; const mixedPlugin = async () =>
import * as azureMonitorPlugin from 'app/plugins/datasource/grafana-azure-monitor-datasource/module'; await import(/* webpackChunkName: "mixedPlugin" */ 'app/plugins/datasource/mixed/module');
const mysqlPlugin = async () =>
await import(/* webpackChunkName: "mysqlPlugin" */ 'app/plugins/datasource/mysql/module');
const postgresPlugin = async () =>
await import(/* webpackChunkName: "postgresPlugin" */ 'app/plugins/datasource/postgres/module');
const prometheusPlugin = async () =>
await import(/* webpackChunkName: "prometheusPlugin" */ 'app/plugins/datasource/prometheus/module');
const mssqlPlugin = async () =>
await import(/* webpackChunkName: "mssqlPlugin" */ 'app/plugins/datasource/mssql/module');
const testDataDSPlugin = async () =>
await import(/* webpackChunkName: "testDataDSPlugin" */ 'app/plugins/datasource/testdata/module');
const inputDatasourcePlugin = async () =>
await import(/* webpackChunkName: "inputDatasourcePlugin" */ 'app/plugins/datasource/input/module');
const stackdriverPlugin = async () =>
await import(/* webpackChunkName: "stackdriverPlugin" */ 'app/plugins/datasource/stackdriver/module');
const azureMonitorPlugin = async () =>
await import(/* webpackChunkName: "azureMonitorPlugin" */ 'app/plugins/datasource/grafana-azure-monitor-datasource/module');
import * as textPanel from 'app/plugins/panel/text/module'; import * as textPanel from 'app/plugins/panel/text/module';
import * as text2Panel from 'app/plugins/panel/text2/module'; import * as text2Panel from 'app/plugins/panel/text2/module';
...@@ -35,7 +51,7 @@ import * as pieChartPanel from 'app/plugins/panel/piechart/module'; ...@@ -35,7 +51,7 @@ import * as pieChartPanel from 'app/plugins/panel/piechart/module';
import * as barGaugePanel from 'app/plugins/panel/bargauge/module'; import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
import * as logsPanel from 'app/plugins/panel/logs/module'; import * as logsPanel from 'app/plugins/panel/logs/module';
import * as exampleApp from 'app/plugins/app/example-app/module'; const exampleApp = async () => await import(/* webpackChunkName: "exampleApp" */ 'app/plugins/app/example-app/module');
const builtInPlugins: any = { const builtInPlugins: any = {
'app/plugins/datasource/graphite/module': graphitePlugin, 'app/plugins/datasource/graphite/module': graphitePlugin,
......
...@@ -162,11 +162,16 @@ for (const flotDep of flotDeps) { ...@@ -162,11 +162,16 @@ for (const flotDep of flotDeps) {
exposeToPlugin(flotDep, { fakeDep: 1 }); exposeToPlugin(flotDep, { fakeDep: 1 });
} }
export function importPluginModule(path: string): Promise<any> { export async function importPluginModule(path: string): Promise<any> {
const builtIn = builtInPlugins[path]; const builtIn = builtInPlugins[path];
if (builtIn) { if (builtIn) {
// for handling dynamic imports
if (typeof builtIn === 'function') {
return await builtIn();
} else {
return Promise.resolve(builtIn); return Promise.resolve(builtIn);
} }
}
return grafanaRuntime.SystemJS.import(path); return grafanaRuntime.SystemJS.import(path);
} }
......
...@@ -68,7 +68,7 @@ export class UsersActionBar extends PureComponent<Props> { ...@@ -68,7 +68,7 @@ export class UsersActionBar extends PureComponent<Props> {
</a> </a>
)} )}
{externalUserMngLinkUrl && ( {externalUserMngLinkUrl && (
<a className="btn btn-primary" href={externalUserMngLinkUrl} target="_blank"> <a className="btn btn-primary" href={externalUserMngLinkUrl} target="_blank" rel="noopener">
<i className="fa fa-external-link-square" /> {externalUserMngLinkName} <i className="fa fa-external-link-square" /> {externalUserMngLinkName}
</a> </a>
)} )}
......
...@@ -86,6 +86,7 @@ exports[`Render should show external user management button 1`] = ` ...@@ -86,6 +86,7 @@ exports[`Render should show external user management button 1`] = `
<a <a
className="btn btn-primary" className="btn btn-primary"
href="some/url" href="some/url"
rel="noopener"
target="_blank" target="_blank"
> >
<i <i
......
import React from 'react'; import React, { Suspense } from 'react';
import { PopoverController, Popover } from '@grafana/ui'; import { PopoverController, Popover } from '@grafana/ui';
// @ts-ignore import { FunctionDescriptor, FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls';
import rst2html from 'rst2html';
import { FunctionDescriptor, FunctionEditorControlsProps, FunctionEditorControls } from './FunctionEditorControls';
interface FunctionEditorProps extends FunctionEditorControlsProps { interface FunctionEditorProps extends FunctionEditorControlsProps {
func: FunctionDescriptor; func: FunctionDescriptor;
...@@ -11,6 +9,15 @@ interface FunctionEditorProps extends FunctionEditorControlsProps { ...@@ -11,6 +9,15 @@ interface FunctionEditorProps extends FunctionEditorControlsProps {
interface FunctionEditorState { interface FunctionEditorState {
showingDescription: boolean; showingDescription: boolean;
} }
const FunctionDescription = React.lazy(async () => {
// @ts-ignore
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
return {
default: (props: { description: string }) => (
<div dangerouslySetInnerHTML={{ __html: rst2html(props.description) }} />
),
};
});
class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEditorState> { class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEditorState> {
private triggerRef = React.createRef<HTMLSpanElement>(); private triggerRef = React.createRef<HTMLSpanElement>();
...@@ -37,11 +44,9 @@ class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEd ...@@ -37,11 +44,9 @@ class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEd
return ( return (
<div style={{ overflow: 'auto', maxHeight: '30rem', textAlign: 'left', fontWeight: 'normal' }}> <div style={{ overflow: 'auto', maxHeight: '30rem', textAlign: 'left', fontWeight: 'normal' }}>
<h4 style={{ color: 'white' }}> {name} </h4> <h4 style={{ color: 'white' }}> {name} </h4>
<div <Suspense fallback={<span>Loading description...</span>}>
dangerouslySetInnerHTML={{ <FunctionDescription description={description} />
__html: rst2html(description), </Suspense>
}}
/>
</div> </div>
); );
} }
......
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
// @ts-ignore // @ts-ignore
import rst2html from 'rst2html';
// @ts-ignore
import Drop from 'tether-drop'; import Drop from 'tether-drop';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import { FuncDef } from './gfunc'; import { FuncDef } from './gfunc';
...@@ -93,7 +91,7 @@ export function graphiteAddFunc($compile: any) { ...@@ -93,7 +91,7 @@ export function graphiteAddFunc($compile: any) {
}; };
$(elem) $(elem)
.on('mouseenter', 'ul.dropdown-menu li', () => { .on('mouseenter', 'ul.dropdown-menu li', async () => {
cleanUpDrop(); cleanUpDrop();
let funcDef; let funcDef;
...@@ -110,6 +108,8 @@ export function graphiteAddFunc($compile: any) { ...@@ -110,6 +108,8 @@ export function graphiteAddFunc($compile: any) {
} }
const contentElement = document.createElement('div'); const contentElement = document.createElement('div');
// @ts-ignore
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc); contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc);
drop = new Drop({ drop = new Drop({
......
...@@ -60,7 +60,7 @@ export default class PromLink extends Component<Props, State> { ...@@ -60,7 +60,7 @@ export default class PromLink extends Component<Props, State> {
render() { render() {
const { href } = this.state; const { href } = this.state;
return ( return (
<a href={href} target="_blank"> <a href={href} target="_blank" rel="noopener">
<i className="fa fa-share-square-o" /> Prometheus <i className="fa fa-share-square-o" /> Prometheus
</a> </a>
); );
......
import React, { FC } from 'react'; import React, { FC } from 'react';
import _ from 'lodash';
import { MetricSelect } from 'app/core/components/Select/MetricSelect'; import { MetricSelect } from 'app/core/components/Select/MetricSelect';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
......
import _ from 'lodash';
import { QueryCtrl } from 'app/plugins/sdk'; import { QueryCtrl } from 'app/plugins/sdk';
import { StackdriverQuery } from './types'; import { StackdriverQuery } from './types';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
......
...@@ -18,7 +18,12 @@ export class TestInfoTab extends PureComponent<Props> { ...@@ -18,7 +18,12 @@ export class TestInfoTab extends PureComponent<Props> {
See github for more information about setting up a reproducable test environment. See github for more information about setting up a reproducable test environment.
<br /> <br />
<br /> <br />
<a className="btn btn-inverse" href="https://github.com/grafana/grafana/tree/master/devenv" target="_blank"> <a
className="btn btn-inverse"
href="https://github.com/grafana/grafana/tree/master/devenv"
target="_blank"
rel="noopener"
>
Github Github
</a> </a>
<br /> <br />
......
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
} }
/* latin */ /* latin */
@font-face { @font-face {
font-display: swap;
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
...@@ -121,6 +122,7 @@ ...@@ -121,6 +122,7 @@
} }
/* latin */ /* latin */
@font-face { @font-face {
font-display: swap;
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
...@@ -184,6 +186,7 @@ ...@@ -184,6 +186,7 @@
} }
/* latin */ /* latin */
@font-face { @font-face {
font-display: swap;
font-family: 'Roboto'; font-family: 'Roboto';
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
......
import _ from 'lodash'; import each from 'lodash/each';
import template from 'lodash/template';
import config from 'app/core/config'; import config from 'app/core/config';
import { dateMath } from '@grafana/data'; import { dateMath } from '@grafana/data';
import { angularMocks, sinon } from '../lib/common'; import { angularMocks, sinon } from '../lib/common';
...@@ -37,7 +39,7 @@ export function ControllerTestContext(this: any) { ...@@ -37,7 +39,7 @@ export function ControllerTestContext(this: any) {
$provide.value('templateSrv', self.templateSrv); $provide.value('templateSrv', self.templateSrv);
$provide.value('$element', self.$element); $provide.value('$element', self.$element);
$provide.value('$sanitize', self.$sanitize); $provide.value('$sanitize', self.$sanitize);
_.each(mocks, (value: any, key: any) => { each(mocks, (value: any, key: any) => {
$provide.value(key, value); $provide.value(key, value);
}); });
}); });
...@@ -118,7 +120,7 @@ export function ServiceTestContext(this: any) { ...@@ -118,7 +120,7 @@ export function ServiceTestContext(this: any) {
this.providePhase = (mocks: any) => { this.providePhase = (mocks: any) => {
return angularMocks.module(($provide: any) => { return angularMocks.module(($provide: any) => {
_.each(mocks, (key: string) => { each(mocks, (key: string) => {
$provide.value(key, self[key]); $provide.value(key, self[key]);
}); });
}); });
...@@ -184,7 +186,7 @@ export function TemplateSrvStub(this: any) { ...@@ -184,7 +186,7 @@ export function TemplateSrvStub(this: any) {
this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g }; this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g };
this.data = {}; this.data = {};
this.replace = (text: string) => { this.replace = (text: string) => {
return _.template(text, this.templateSettings)(this.data); return template(text, this.templateSettings)(this.data);
}; };
this.init = () => {}; this.init = () => {};
this.getAdhocFilters = (): any => { this.getAdhocFilters = (): any => {
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<base href="[[.AppSubUrl]]/" /> <base href="[[.AppSubUrl]]/" />
<link rel="preload" href="public/fonts/roboto/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2" as="font" crossorigin />
<link rel="icon" type="image/png" href="public/img/fav32.png"> <link rel="icon" type="image/png" href="public/img/fav32.png">
<link rel="mask-icon" href="public/img/grafana_mask_icon.svg" color="#F05A28"> <link rel="mask-icon" href="public/img/grafana_mask_icon.svg" color="#F05A28">
<link rel="apple-touch-icon" sizes="180x180" href="public/img/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="public/img/apple-touch-icon.png">
......
'use strict'; 'use strict';
const pkg = require('../../package.json'); const pkg = require('../../package.json');
const _ = require('lodash'); const pull = require('lodash/pull');
let dependencies = Object.keys(pkg.dependencies); let dependencies = Object.keys(pkg.dependencies);
// remove jquery so we can add it first // remove jquery so we can add it first
// remove rxjs so we can only depend on parts of it in code // remove rxjs so we can only depend on parts of it in code
_.pull(dependencies, 'jquery', 'rxjs') pull(dependencies, 'jquery', 'rxjs')
// add jquery first // add jquery first
dependencies.unshift('jquery'); dependencies.unshift('jquery');
......
const path = require('path'); const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = { module.exports = {
target: 'web', target: 'web',
...@@ -13,63 +14,88 @@ module.exports = { ...@@ -13,63 +14,88 @@ module.exports = {
}, },
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'], extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
alias: { alias: {},
}, modules: [path.resolve('public'), path.resolve('node_modules')],
modules: [
path.resolve('public'),
path.resolve('node_modules')
],
}, },
stats: { stats: {
children: false, children: false,
warningsFilter: /export .* was not found in/ warningsFilter: /export .* was not found in/,
source: false
}, },
node: { node: {
fs: 'empty', fs: 'empty',
}, },
module: { module: {
rules: [ rules: [{
{
test: require.resolve('jquery'), test: require.resolve('jquery'),
use: [ use: [{
{
loader: 'expose-loader', loader: 'expose-loader',
query: 'jQuery' query: 'jQuery',
}, },
{ {
loader: 'expose-loader', loader: 'expose-loader',
query: '$' query: '$',
} },
] ],
}, },
{ {
test: /\.html$/, test: /\.html$/,
exclude: /(index|error)\-template\.html/, exclude: /(index|error)\-template\.html/,
use: [ use: [{
{ loader: 'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname, '../../public')) + '&prefix=public' }, loader: 'ngtemplate-loader?relativeTo=' + path.resolve(__dirname, '../../public') + '&prefix=public',
},
{ {
loader: 'html-loader', loader: 'html-loader',
options: { options: {
attrs: [], attrs: [],
minimize: true, minimize: true,
removeComments: false, removeComments: false,
collapseWhitespace: false collapseWhitespace: false,
} },
} },
] ],
} },
] ],
}, },
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3 // https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
optimization: { optimization: {
moduleIds: 'hashed',
runtimeChunk: 'single',
splitChunks: { splitChunks: {
chunks: 'all',
minChunks: 1,
cacheGroups: { cacheGroups: {
commons: { moment: {
test: /[\\/]node_modules[\\/]moment[\\/].*[jt]sx?$/,
chunks: 'initial',
priority: 20,
enforce: true
},
angular: {
test: /[\\/]node_modules[\\/]angular[\\/].*[jt]sx?$/,
chunks: 'initial',
priority: 50,
enforce: true
},
vendors: {
test: /[\\/]node_modules[\\/].*[jt]sx?$/, test: /[\\/]node_modules[\\/].*[jt]sx?$/,
name: 'vendor', chunks: 'initial',
chunks: 'all' priority: -10,
} reuseExistingChunk: true,
} enforce: true
} },
} default: {
priority: -20,
chunks: 'all',
test: /.*[jt]sx?$/,
reuseExistingChunk: true
},
},
},
},
plugins: [
new ForkTsCheckerWebpackPlugin({
checkSyntacticErrors: true,
})
],
}; };
...@@ -27,8 +27,7 @@ module.exports = (env = {}) => ...@@ -27,8 +27,7 @@ module.exports = (env = {}) =>
}, },
module: { module: {
rules: [ rules: [{
{
test: /\.tsx?$/, test: /\.tsx?$/,
enforce: 'pre', enforce: 'pre',
exclude: /node_modules/, exclude: /node_modules/,
...@@ -37,8 +36,8 @@ module.exports = (env = {}) => ...@@ -37,8 +36,8 @@ module.exports = (env = {}) =>
options: { options: {
emitErrors: true, emitErrors: true,
typeCheck: false, typeCheck: false,
}, }
}, }
}, },
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
...@@ -46,48 +45,55 @@ module.exports = (env = {}) => ...@@ -46,48 +45,55 @@ module.exports = (env = {}) =>
use: { use: {
loader: 'ts-loader', loader: 'ts-loader',
options: { options: {
transpileOnly: true, transpileOnly: true
}, },
}, },
}, },
require('./sass.rule.js')({ sourceMap: false, preserveUrl: false }), require('./sass.rule.js')({
sourceMap: false,
preserveUrl: false
}),
{ {
test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
loader: 'file-loader', loader: 'file-loader'
}, },
], ]
}, },
plugins: [ plugins: [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
env.noTsCheck env.noTsCheck ?
? new webpack.DefinePlugin({}) // bogus plugin to satisfy webpack API new webpack.DefinePlugin({}) // bogus plugin to satisfy webpack API
: new ForkTsCheckerWebpackPlugin({ :
new ForkTsCheckerWebpackPlugin({
checkSyntacticErrors: true, checkSyntacticErrors: true,
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: 'grafana.[name].[hash].css', filename: "grafana.[name].[hash].css"
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/error.html'), filename: path.resolve(__dirname, '../../public/views/error.html'),
template: path.resolve(__dirname, '../../public/views/error-template.html'), template: path.resolve(__dirname, '../../public/views/error-template.html'),
inject: false, inject: false,
chunksSortMode: 'none',
excludeChunks: ['dark', 'light']
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/index.html'), filename: path.resolve(__dirname, '../../public/views/index.html'),
template: path.resolve(__dirname, '../../public/views/index-template.html'), template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: 'body', inject: 'body',
chunks: ['manifest', 'vendor', 'app'], chunksSortMode: 'none',
excludeChunks: ['dark', 'light']
}), }),
new webpack.NamedModulesPlugin(), new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify('development'), 'NODE_ENV': JSON.stringify('development')
}, }
}), }),
// new BundleAnalyzerPlugin({ // new BundleAnalyzerPlugin({
// analyzerPort: 8889 // analyzerPort: 8889
// }) // })
], ]
}); });
...@@ -42,23 +42,24 @@ module.exports = merge(common, { ...@@ -42,23 +42,24 @@ module.exports = merge(common, {
optimization: { optimization: {
removeAvailableModules: false, removeAvailableModules: false,
runtimeChunk: false,
removeEmptyChunks: false, removeEmptyChunks: false,
splitChunks: false, splitChunks: false
}, },
module: { module: {
rules: [ rules: [{
{
test: /\.tsx?$/, test: /\.tsx?$/,
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [{
{
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
cacheDirectory: true, cacheDirectory: true,
babelrc: false, babelrc: false,
plugins: [ plugins: [
[require('@rtsao/plugin-proposal-class-properties'), { loose: true }], [require('@rtsao/plugin-proposal-class-properties'), {
loose: true
}],
'angularjs-annotate', 'angularjs-annotate',
'@babel/plugin-syntax-dynamic-import', // needed for `() => import()` in routes.ts '@babel/plugin-syntax-dynamic-import', // needed for `() => import()` in routes.ts
'react-hot-loader/babel', 'react-hot-loader/babel',
...@@ -67,16 +68,18 @@ module.exports = merge(common, { ...@@ -67,16 +68,18 @@ module.exports = merge(common, {
[ [
'@babel/preset-env', '@babel/preset-env',
{ {
targets: { browsers: 'last 3 versions' }, targets: {
browsers: 'last 3 versions'
},
useBuiltIns: 'entry', useBuiltIns: 'entry',
modules: false
}, },
], ],
'@babel/preset-typescript', '@babel/preset-typescript',
'@babel/preset-react', '@babel/preset-react',
], ],
}, },
}, }, ],
],
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
...@@ -86,7 +89,9 @@ module.exports = merge(common, { ...@@ -86,7 +89,9 @@ module.exports = merge(common, {
{ {
loader: 'postcss-loader', loader: 'postcss-loader',
options: { options: {
config: { path: __dirname + '/postcss.config.js' }, config: {
path: __dirname + '/postcss.config.js'
},
}, },
}, },
{ {
...@@ -108,6 +113,7 @@ module.exports = merge(common, { ...@@ -108,6 +113,7 @@ module.exports = merge(common, {
template: path.resolve(__dirname, '../../public/views/index-template.html'), template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: 'body', inject: 'body',
alwaysWriteToDisk: true, alwaysWriteToDisk: true,
chunksSortMode: 'none'
}), }),
new HtmlWebpackHarddiskPlugin(), new HtmlWebpackHarddiskPlugin(),
new webpack.NamedModulesPlugin(), new webpack.NamedModulesPlugin(),
......
...@@ -20,8 +20,7 @@ module.exports = merge(common, { ...@@ -20,8 +20,7 @@ module.exports = merge(common, {
}, },
module: { module: {
rules: [ rules: [{
{
test: /\.tsx?$/, test: /\.tsx?$/,
enforce: 'pre', enforce: 'pre',
exclude: /node_modules/, exclude: /node_modules/,
...@@ -44,11 +43,13 @@ module.exports = merge(common, { ...@@ -44,11 +43,13 @@ module.exports = merge(common, {
}, },
}, },
require('./sass.rule.js')({ require('./sass.rule.js')({
sourceMap: false, preserveUrl: false sourceMap: false,
preserveUrl: false
}) })
] ]
}, },
optimization: { optimization: {
nodeEnv: 'production',
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
cache: false, cache: false,
...@@ -70,12 +71,15 @@ module.exports = merge(common, { ...@@ -70,12 +71,15 @@ module.exports = merge(common, {
filename: path.resolve(__dirname, '../../public/views/error.html'), filename: path.resolve(__dirname, '../../public/views/error.html'),
template: path.resolve(__dirname, '../../public/views/error-template.html'), template: path.resolve(__dirname, '../../public/views/error-template.html'),
inject: false, inject: false,
excludeChunks: ['dark', 'light'],
chunksSortMode: 'none'
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: path.resolve(__dirname, '../../public/views/index.html'), filename: path.resolve(__dirname, '../../public/views/index.html'),
template: path.resolve(__dirname, '../../public/views/index-template.html'), template: path.resolve(__dirname, '../../public/views/index-template.html'),
inject: 'body', inject: 'body',
chunks: ['vendor', 'app'], excludeChunks: ['manifest', 'dark', 'light'],
chunksSortMode: 'none'
}), }),
function () { function () {
this.hooks.done.tap('Done', function (stats) { this.hooks.done.tap('Done', function (stats) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment