mirror of
https://github.com/grafana/grafana.git
synced 2025-09-17 19:32:51 +08:00
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
This commit is contained in:
7
.babelrc
7
.babelrc
@ -3,8 +3,11 @@
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": { "browsers": "last 3 versions" },
|
||||
"useBuiltIns": "entry"
|
||||
"targets": {
|
||||
"browsers": "last 3 versions"
|
||||
},
|
||||
"useBuiltIns": "entry",
|
||||
"modules": "false",
|
||||
}
|
||||
]
|
||||
]
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -94,3 +94,6 @@ theOutput/
|
||||
|
||||
# Ignore go local build dependencies
|
||||
/scripts/go/bin/**
|
||||
|
||||
# Ignore compilation stats from `yarn stats`
|
||||
compilation-stats.json
|
||||
|
@ -147,6 +147,7 @@
|
||||
"start:hot": "grafana-toolkit core:start --hot --watchTheme",
|
||||
"start:ignoreTheme": "grafana-toolkit core:start --hot",
|
||||
"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 ",
|
||||
"build": "grunt build",
|
||||
"test": "grunt test",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import difference from 'lodash/difference';
|
||||
|
||||
import { fieldReducers, ReducerID, reduceField } from './fieldReducer';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { Field, FieldType } from '../types/index';
|
||||
import { MutableDataFrame } from './dataFrameHelper';
|
||||
import { ArrayVector } from './vector';
|
||||
@ -42,7 +43,7 @@ describe('Stats Calculators', () => {
|
||||
expect(stats.length).toBe(2);
|
||||
|
||||
const found = stats.map(v => v.id);
|
||||
const notFound = _.difference(names, found);
|
||||
const notFound = difference(names, found);
|
||||
expect(notFound.length).toBe(2);
|
||||
|
||||
expect(notFound[0]).toBe('not a stat');
|
||||
|
@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
import _ from 'lodash';
|
||||
import each from 'lodash/each';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
|
||||
import { RawTimeRange } from '../types/time';
|
||||
|
||||
@ -64,12 +64,12 @@ const rangeOptions = [
|
||||
const absoluteFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
const rangeIndex: any = {};
|
||||
_.each(rangeOptions, (frame: any) => {
|
||||
each(rangeOptions, (frame: any) => {
|
||||
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
||||
});
|
||||
|
||||
export function getRelativeTimesList(timepickerSettings: any, currentDisplay: any) {
|
||||
const groups = _.groupBy(rangeOptions, (option: any) => {
|
||||
const groups = groupBy(rangeOptions, (option: any) => {
|
||||
option.active = option.display === currentDisplay;
|
||||
return option.section;
|
||||
});
|
||||
|
@ -183,7 +183,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
presets: ['@babel/preset-env', { modules: false }],
|
||||
plugins: ['angularjs-annotate'],
|
||||
},
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { GraphSeriesValue, AbsoluteTimeRange } from '@grafana/data';
|
||||
|
@ -16,22 +16,22 @@ export const Footer: FC<Props> = React.memo(
|
||||
<div className="text-center">
|
||||
<ul>
|
||||
<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
|
||||
</a>
|
||||
</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
|
||||
</a>
|
||||
</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
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://grafana.com" target="_blank">
|
||||
<a href="https://grafana.com" target="_blank" rel="noopener">
|
||||
{appName}
|
||||
</a>{' '}
|
||||
<span>
|
||||
@ -41,7 +41,7 @@ export const Footer: FC<Props> = React.memo(
|
||||
{newGrafanaVersionExists && (
|
||||
<li>
|
||||
<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!
|
||||
</a>
|
||||
</Tooltip>
|
||||
|
4
public/app/core/components/code_editor/brace.d.ts
vendored
Normal file
4
public/app/core/components/code_editor/brace.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module 'brace/*' {
|
||||
let brace: any;
|
||||
export default brace;
|
||||
}
|
@ -30,20 +30,6 @@
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
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_LIGHT = 'ace/theme/textmate';
|
||||
@ -55,7 +41,7 @@ const DEFAULT_SNIPPETS = true;
|
||||
|
||||
const editorTemplate = `<div></div>`;
|
||||
|
||||
function link(scope: any, elem: any, attrs: any) {
|
||||
async function link(scope: any, elem: any, attrs: any) {
|
||||
// Options
|
||||
const langMode = attrs.mode || DEFAULT_MODE;
|
||||
const maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
|
||||
@ -66,6 +52,23 @@ function link(scope: any, elem: any, attrs: any) {
|
||||
|
||||
// Initialize editor
|
||||
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 editorSession = codeEditor.getSession();
|
||||
|
||||
|
@ -160,6 +160,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
<li>
|
||||
<a
|
||||
href="http://docs.grafana.org"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<i
|
||||
@ -171,6 +172,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
<li>
|
||||
<a
|
||||
href="https://grafana.com/services/support"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<i
|
||||
@ -182,6 +184,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
<li>
|
||||
<a
|
||||
href="https://community.grafana.com/"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<i
|
||||
@ -193,6 +196,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
||||
<li>
|
||||
<a
|
||||
href="https://grafana.com"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
Grafana
|
||||
|
@ -131,6 +131,7 @@ class NewDataSourcePage extends PureComponent<Props> {
|
||||
className="btn btn-inverse"
|
||||
href="https://grafana.com/plugins?type=datasource&utm_source=new-data-source"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Find more data source plugins on grafana.com
|
||||
</a>
|
||||
@ -198,6 +199,7 @@ const DataSourceTypeCard: FC<DataSourceTypeCardProps> = props => {
|
||||
className="btn btn-inverse"
|
||||
href={`${learnMoreLink}?utm_source=grafana_add_ds`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={onLearnMoreClick}
|
||||
>
|
||||
Learn more <i className="fa fa-external-link add-datasource-item-actions__btn-icon" />
|
||||
|
@ -14,6 +14,7 @@ export const NoDataSourceCallToAction = () => {
|
||||
<a
|
||||
href="http://docs.grafana.org/administration/provisioning/#datasources?utm_source=explore"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="text-link"
|
||||
>
|
||||
Learn more
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
import debounce from 'lodash/debounce';
|
||||
import has from 'lodash/has';
|
||||
import { hot } from 'react-hot-loader';
|
||||
// @ts-ignore
|
||||
import { connect } from 'react-redux';
|
||||
@ -97,7 +98,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
|
||||
this.setState({ textEditModeEnabled: !this.state.textEditModeEnabled });
|
||||
};
|
||||
|
||||
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
||||
updateLogsHighlights = debounce((value: DataQuery) => {
|
||||
const { datasourceInstance } = this.props;
|
||||
if (datasourceInstance.getHighlighterExpression) {
|
||||
const { exploreId } = this.props;
|
||||
@ -120,7 +121,7 @@ export class QueryRow extends PureComponent<QueryRowProps, QueryRowState> {
|
||||
mode,
|
||||
} = this.props;
|
||||
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] : [];
|
||||
let QueryField;
|
||||
|
||||
|
@ -278,7 +278,7 @@ class PluginPage extends PureComponent<Props, State> {
|
||||
{info.links.map(link => {
|
||||
return (
|
||||
<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}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -1,20 +1,36 @@
|
||||
import * as graphitePlugin from 'app/plugins/datasource/graphite/module';
|
||||
import * as cloudwatchPlugin from 'app/plugins/datasource/cloudwatch/module';
|
||||
import * as dashboardDSPlugin from 'app/plugins/datasource/dashboard/module';
|
||||
import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/module';
|
||||
import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module';
|
||||
import * as grafanaPlugin from 'app/plugins/datasource/grafana/module';
|
||||
import * as influxdbPlugin from 'app/plugins/datasource/influxdb/module';
|
||||
import * as lokiPlugin from 'app/plugins/datasource/loki/module';
|
||||
import * as mixedPlugin from 'app/plugins/datasource/mixed/module';
|
||||
import * as mysqlPlugin from 'app/plugins/datasource/mysql/module';
|
||||
import * as postgresPlugin from 'app/plugins/datasource/postgres/module';
|
||||
import * as prometheusPlugin from 'app/plugins/datasource/prometheus/module';
|
||||
import * as mssqlPlugin from 'app/plugins/datasource/mssql/module';
|
||||
import * as testDataDSPlugin from 'app/plugins/datasource/testdata/module';
|
||||
import * as inputDatasourcePlugin from 'app/plugins/datasource/input/module';
|
||||
import * as stackdriverPlugin from 'app/plugins/datasource/stackdriver/module';
|
||||
import * as azureMonitorPlugin from 'app/plugins/datasource/grafana-azure-monitor-datasource/module';
|
||||
const graphitePlugin = async () =>
|
||||
await import(/* webpackChunkName: "graphitePlugin" */ 'app/plugins/datasource/graphite/module');
|
||||
const cloudwatchPlugin = async () =>
|
||||
await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module');
|
||||
const dashboardDSPlugin = async () =>
|
||||
await import(/* webpackChunkName "dashboardDSPlugin" */ 'app/plugins/datasource/dashboard/module');
|
||||
const elasticsearchPlugin = async () =>
|
||||
await import(/* webpackChunkName: "elasticsearchPlugin" */ 'app/plugins/datasource/elasticsearch/module');
|
||||
const opentsdbPlugin = async () =>
|
||||
await import(/* webpackChunkName: "opentsdbPlugin" */ 'app/plugins/datasource/opentsdb/module');
|
||||
const grafanaPlugin = async () =>
|
||||
await import(/* webpackChunkName: "grafanaPlugin" */ 'app/plugins/datasource/grafana/module');
|
||||
const influxdbPlugin = async () =>
|
||||
await import(/* webpackChunkName: "influxdbPlugin" */ 'app/plugins/datasource/influxdb/module');
|
||||
const lokiPlugin = async () => await import(/* webpackChunkName: "lokiPlugin" */ 'app/plugins/datasource/loki/module');
|
||||
const mixedPlugin = async () =>
|
||||
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 text2Panel from 'app/plugins/panel/text2/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 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 = {
|
||||
'app/plugins/datasource/graphite/module': graphitePlugin,
|
||||
|
@ -162,11 +162,16 @@ for (const flotDep of flotDeps) {
|
||||
exposeToPlugin(flotDep, { fakeDep: 1 });
|
||||
}
|
||||
|
||||
export function importPluginModule(path: string): Promise<any> {
|
||||
export async function importPluginModule(path: string): Promise<any> {
|
||||
const builtIn = builtInPlugins[path];
|
||||
if (builtIn) {
|
||||
// for handling dynamic imports
|
||||
if (typeof builtIn === 'function') {
|
||||
return await builtIn();
|
||||
} else {
|
||||
return Promise.resolve(builtIn);
|
||||
}
|
||||
}
|
||||
return grafanaRuntime.SystemJS.import(path);
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ export class UsersActionBar extends PureComponent<Props> {
|
||||
</a>
|
||||
)}
|
||||
{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}
|
||||
</a>
|
||||
)}
|
||||
|
@ -86,6 +86,7 @@ exports[`Render should show external user management button 1`] = `
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href="some/url"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<i
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { PopoverController, Popover } from '@grafana/ui';
|
||||
// @ts-ignore
|
||||
import rst2html from 'rst2html';
|
||||
import { FunctionDescriptor, FunctionEditorControlsProps, FunctionEditorControls } from './FunctionEditorControls';
|
||||
import { FunctionDescriptor, FunctionEditorControls, FunctionEditorControlsProps } from './FunctionEditorControls';
|
||||
|
||||
interface FunctionEditorProps extends FunctionEditorControlsProps {
|
||||
func: FunctionDescriptor;
|
||||
@ -11,6 +9,15 @@ interface FunctionEditorProps extends FunctionEditorControlsProps {
|
||||
interface FunctionEditorState {
|
||||
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> {
|
||||
private triggerRef = React.createRef<HTMLSpanElement>();
|
||||
@ -37,11 +44,9 @@ class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEd
|
||||
return (
|
||||
<div style={{ overflow: 'auto', maxHeight: '30rem', textAlign: 'left', fontWeight: 'normal' }}>
|
||||
<h4 style={{ color: 'white' }}> {name} </h4>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: rst2html(description),
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={<span>Loading description...</span>}>
|
||||
<FunctionDescription description={description} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
// @ts-ignore
|
||||
import rst2html from 'rst2html';
|
||||
// @ts-ignore
|
||||
import Drop from 'tether-drop';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { FuncDef } from './gfunc';
|
||||
@ -93,7 +91,7 @@ export function graphiteAddFunc($compile: any) {
|
||||
};
|
||||
|
||||
$(elem)
|
||||
.on('mouseenter', 'ul.dropdown-menu li', () => {
|
||||
.on('mouseenter', 'ul.dropdown-menu li', async () => {
|
||||
cleanUpDrop();
|
||||
|
||||
let funcDef;
|
||||
@ -110,6 +108,8 @@ export function graphiteAddFunc($compile: any) {
|
||||
}
|
||||
|
||||
const contentElement = document.createElement('div');
|
||||
// @ts-ignore
|
||||
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
|
||||
contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc);
|
||||
|
||||
drop = new Drop({
|
||||
|
@ -60,7 +60,7 @@ export default class PromLink extends Component<Props, State> {
|
||||
render() {
|
||||
const { href } = this.state;
|
||||
return (
|
||||
<a href={href} target="_blank">
|
||||
<a href={href} target="_blank" rel="noopener">
|
||||
<i className="fa fa-share-square-o" /> Prometheus
|
||||
</a>
|
||||
);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { MetricSelect } from 'app/core/components/Select/MetricSelect';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
@ -1,5 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
import { StackdriverQuery } from './types';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
@ -18,7 +18,12 @@ export class TestInfoTab extends PureComponent<Props> {
|
||||
See github for more information about setting up a reproducable test environment.
|
||||
<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
|
||||
</a>
|
||||
<br />
|
||||
|
@ -3,28 +3,9 @@ import './ReactContainer';
|
||||
import { applyRouteRegistrationHandlers } from './registry';
|
||||
|
||||
// Pages
|
||||
import ChangePasswordPage from 'app/features/profile/ChangePasswordPage';
|
||||
import ServerStats from 'app/features/admin/ServerStats';
|
||||
import AlertRuleList from 'app/features/alerting/AlertRuleList';
|
||||
import TeamPages from 'app/features/teams/TeamPages';
|
||||
import TeamList from 'app/features/teams/TeamList';
|
||||
import ApiKeys from 'app/features/api-keys/ApiKeysPage';
|
||||
import PluginListPage from 'app/features/plugins/PluginListPage';
|
||||
import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
|
||||
import FolderPermissions from 'app/features/folders/FolderPermissions';
|
||||
import CreateFolderCtrl from 'app/features/folders/CreateFolderCtrl';
|
||||
import FolderDashboardsCtrl from 'app/features/folders/FolderDashboardsCtrl';
|
||||
import DashboardImportCtrl from 'app/features/manage-dashboards/DashboardImportCtrl';
|
||||
import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
|
||||
import NewDataSourcePage from '../features/datasources/NewDataSourcePage';
|
||||
import UsersListPage from 'app/features/users/UsersListPage';
|
||||
import DataSourceDashboards from 'app/features/datasources/DataSourceDashboards';
|
||||
import DataSourceSettingsPage from '../features/datasources/settings/DataSourceSettingsPage';
|
||||
import OrgDetailsPage from '../features/org/OrgDetailsPage';
|
||||
import SoloPanelPage from '../features/dashboard/containers/SoloPanelPage';
|
||||
import DashboardPage from '../features/dashboard/containers/DashboardPage';
|
||||
import PluginPage from '../features/plugins/PluginPage';
|
||||
import AppRootPage from 'app/features/plugins/AppRootPage';
|
||||
import config from 'app/core/config';
|
||||
import { route, ILocationProvider } from 'angular';
|
||||
|
||||
@ -39,6 +20,9 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
// Routes here are guarded both here and server side for react-container routes or just on the server for angular
|
||||
// ones. That means angular ones could be navigated to in case there is a client side link some where.
|
||||
|
||||
const importDashboardPage = () =>
|
||||
import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPage');
|
||||
|
||||
$routeProvider
|
||||
.when('/', {
|
||||
template: '<react-container />',
|
||||
@ -47,7 +31,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.Home,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => DashboardPage,
|
||||
component: importDashboardPage,
|
||||
},
|
||||
})
|
||||
.when('/d/:uid/:slug', {
|
||||
@ -56,7 +40,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => DashboardPage,
|
||||
component: importDashboardPage,
|
||||
},
|
||||
})
|
||||
.when('/d/:uid', {
|
||||
@ -65,7 +49,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
reloadOnSearch: false,
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
resolve: {
|
||||
component: () => DashboardPage,
|
||||
component: importDashboardPage,
|
||||
},
|
||||
})
|
||||
.when('/dashboard/:type/:slug', {
|
||||
@ -74,7 +58,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => DashboardPage,
|
||||
component: importDashboardPage,
|
||||
},
|
||||
})
|
||||
.when('/dashboard/new', {
|
||||
@ -83,7 +67,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.New,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => DashboardPage,
|
||||
component: importDashboardPage,
|
||||
},
|
||||
})
|
||||
.when('/d-solo/:uid/:slug', {
|
||||
@ -92,7 +76,8 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => SoloPanelPage,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage'),
|
||||
},
|
||||
})
|
||||
.when('/dashboard-solo/:type/:slug', {
|
||||
@ -101,7 +86,8 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
routeInfo: DashboardRouteInfo.Normal,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => SoloPanelPage,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage'),
|
||||
},
|
||||
})
|
||||
.when('/dashboard/import', {
|
||||
@ -112,26 +98,29 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/datasources', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => DataSourcesListPage,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "DataSourcesListPage"*/ 'app/features/datasources/DataSourcesListPage'),
|
||||
},
|
||||
})
|
||||
.when('/datasources/edit/:id/', {
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false, // for tabs
|
||||
resolve: {
|
||||
component: () => DataSourceSettingsPage,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "DataSourceSettingsPage"*/ '../features/datasources/settings/DataSourceSettingsPage'),
|
||||
},
|
||||
})
|
||||
.when('/datasources/edit/:id/dashboards', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => DataSourceDashboards,
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "DataSourceDashboards"*/ 'app/features/datasources/DataSourceDashboards'),
|
||||
},
|
||||
})
|
||||
.when('/datasources/new', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => NewDataSourcePage,
|
||||
component: () => import(/* webpackChunkName: "NewDataSourcePage"*/ '../features/datasources/NewDataSourcePage'),
|
||||
},
|
||||
})
|
||||
.when('/dashboards', {
|
||||
@ -147,13 +136,13 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/dashboards/f/:uid/:slug/permissions', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => FolderPermissions,
|
||||
component: () => import(/* webpackChunkName: "FolderPermissions"*/ 'app/features/folders/FolderPermissions'),
|
||||
},
|
||||
})
|
||||
.when('/dashboards/f/:uid/:slug/settings', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => FolderSettingsPage,
|
||||
component: () => import(/* webpackChunkName: "FolderSettingsPage"*/ 'app/features/folders/FolderSettingsPage'),
|
||||
},
|
||||
})
|
||||
.when('/dashboards/f/:uid/:slug', {
|
||||
@ -179,13 +168,13 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => AppRootPage,
|
||||
component: () => import(/* webpackChunkName: "AppRootPage" */ 'app/features/plugins/AppRootPage'),
|
||||
},
|
||||
})
|
||||
.when('/org', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => OrgDetailsPage,
|
||||
component: () => import(/* webpackChunkName: "OrgDetailsPage" */ '../features/org/OrgDetailsPage'),
|
||||
},
|
||||
})
|
||||
.when('/org/new', {
|
||||
@ -195,7 +184,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/org/users', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => UsersListPage,
|
||||
component: () => import(/* webpackChunkName: "UsersListPage" */ 'app/features/users/UsersListPage'),
|
||||
},
|
||||
})
|
||||
.when('/org/users/invite', {
|
||||
@ -207,14 +196,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
roles: () => ['Editor', 'Admin'],
|
||||
component: () => ApiKeys,
|
||||
component: () => import(/* webpackChunkName: "ApiKeysPage" */ 'app/features/api-keys/ApiKeysPage'),
|
||||
},
|
||||
})
|
||||
.when('/org/teams', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
roles: () => (config.editorsCanAdmin ? [] : ['Editor', 'Admin']),
|
||||
component: () => TeamList,
|
||||
component: () => import(/* webpackChunkName: "TeamList" */ 'app/features/teams/TeamList'),
|
||||
},
|
||||
})
|
||||
.when('/org/teams/new', {
|
||||
@ -226,7 +215,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
roles: () => (config.editorsCanAdmin ? [] : ['Admin']),
|
||||
component: () => TeamPages,
|
||||
component: () => import(/* webpackChunkName: "TeamPages" */ 'app/features/teams/TeamPages'),
|
||||
},
|
||||
})
|
||||
.when('/profile', {
|
||||
@ -237,7 +226,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/profile/password', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => ChangePasswordPage,
|
||||
component: () => import(/* webPackChunkName: "ChangePasswordPage" */ 'app/features/profile/ChangePasswordPage'),
|
||||
},
|
||||
})
|
||||
.when('/profile/select-org', {
|
||||
@ -281,7 +270,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/admin/stats', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => ServerStats,
|
||||
component: () => import(/* webpackChunkName: "ServerStats" */ 'app/features/admin/ServerStats'),
|
||||
},
|
||||
})
|
||||
// LOGIN / SIGNUP
|
||||
@ -320,14 +309,14 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
.when('/plugins', {
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => PluginListPage,
|
||||
component: () => import(/* webpackChunkName: "PluginListPage" */ 'app/features/plugins/PluginListPage'),
|
||||
},
|
||||
})
|
||||
.when('/plugins/:pluginId/', {
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false, // tabs from query parameters
|
||||
resolve: {
|
||||
component: () => PluginPage,
|
||||
component: () => import(/* webpackChunkName: "PluginPage" */ '../features/plugins/PluginPage'),
|
||||
},
|
||||
})
|
||||
.when('/plugins/:pluginId/page/:slug', {
|
||||
@ -347,7 +336,7 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
template: '<react-container />',
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
component: () => AlertRuleList,
|
||||
component: () => import(/* webpackChunkName: "AlertRuleList" */ 'app/features/alerting/AlertRuleList'),
|
||||
},
|
||||
})
|
||||
.when('/alerting/notifications', {
|
||||
|
@ -59,6 +59,7 @@
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@ -121,6 +122,7 @@
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
@ -184,6 +186,7 @@
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
|
@ -1,4 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import each from 'lodash/each';
|
||||
import template from 'lodash/template';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { dateMath } from '@grafana/data';
|
||||
import { angularMocks, sinon } from '../lib/common';
|
||||
@ -37,7 +39,7 @@ export function ControllerTestContext(this: any) {
|
||||
$provide.value('templateSrv', self.templateSrv);
|
||||
$provide.value('$element', self.$element);
|
||||
$provide.value('$sanitize', self.$sanitize);
|
||||
_.each(mocks, (value: any, key: any) => {
|
||||
each(mocks, (value: any, key: any) => {
|
||||
$provide.value(key, value);
|
||||
});
|
||||
});
|
||||
@ -118,7 +120,7 @@ export function ServiceTestContext(this: any) {
|
||||
|
||||
this.providePhase = (mocks: any) => {
|
||||
return angularMocks.module(($provide: any) => {
|
||||
_.each(mocks, (key: string) => {
|
||||
each(mocks, (key: string) => {
|
||||
$provide.value(key, self[key]);
|
||||
});
|
||||
});
|
||||
@ -184,7 +186,7 @@ export function TemplateSrvStub(this: any) {
|
||||
this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g };
|
||||
this.data = {};
|
||||
this.replace = (text: string) => {
|
||||
return _.template(text, this.templateSettings)(this.data);
|
||||
return template(text, this.templateSettings)(this.data);
|
||||
};
|
||||
this.init = () => {};
|
||||
this.getAdhocFilters = (): any => {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
<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="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">
|
||||
|
@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const pkg = require('../../package.json');
|
||||
const _ = require('lodash');
|
||||
const pull = require('lodash/pull');
|
||||
|
||||
let dependencies = Object.keys(pkg.dependencies);
|
||||
// remove jquery so we can add it first
|
||||
// remove rxjs so we can only depend on parts of it in code
|
||||
_.pull(dependencies, 'jquery', 'rxjs')
|
||||
pull(dependencies, 'jquery', 'rxjs')
|
||||
|
||||
// add jquery first
|
||||
dependencies.unshift('jquery');
|
||||
|
@ -1,4 +1,5 @@
|
||||
const path = require('path');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
target: 'web',
|
||||
@ -13,63 +14,88 @@ module.exports = {
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
|
||||
alias: {
|
||||
},
|
||||
modules: [
|
||||
path.resolve('public'),
|
||||
path.resolve('node_modules')
|
||||
],
|
||||
alias: {},
|
||||
modules: [path.resolve('public'), path.resolve('node_modules')],
|
||||
},
|
||||
stats: {
|
||||
children: false,
|
||||
warningsFilter: /export .* was not found in/
|
||||
warningsFilter: /export .* was not found in/,
|
||||
source: false
|
||||
},
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
rules: [{
|
||||
test: require.resolve('jquery'),
|
||||
use: [
|
||||
{
|
||||
use: [{
|
||||
loader: 'expose-loader',
|
||||
query: 'jQuery'
|
||||
query: 'jQuery',
|
||||
},
|
||||
{
|
||||
loader: 'expose-loader',
|
||||
query: '$'
|
||||
}
|
||||
]
|
||||
query: '$',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
exclude: /(index|error)\-template\.html/,
|
||||
use: [
|
||||
{ loader: 'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname, '../../public')) + '&prefix=public' },
|
||||
use: [{
|
||||
loader: 'ngtemplate-loader?relativeTo=' + path.resolve(__dirname, '../../public') + '&prefix=public',
|
||||
},
|
||||
{
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
attrs: [],
|
||||
minimize: true,
|
||||
removeComments: false,
|
||||
collapseWhitespace: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
collapseWhitespace: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
|
||||
optimization: {
|
||||
moduleIds: 'hashed',
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minChunks: 1,
|
||||
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?$/,
|
||||
name: 'vendor',
|
||||
chunks: 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chunks: 'initial',
|
||||
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 = {}) =>
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
rules: [{
|
||||
test: /\.tsx?$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
@ -37,8 +36,8 @@ module.exports = (env = {}) =>
|
||||
options: {
|
||||
emitErrors: true,
|
||||
typeCheck: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
@ -46,48 +45,55 @@ module.exports = (env = {}) =>
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
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=&.]+)?$/,
|
||||
loader: 'file-loader',
|
||||
loader: 'file-loader'
|
||||
},
|
||||
],
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
env.noTsCheck
|
||||
? new webpack.DefinePlugin({}) // bogus plugin to satisfy webpack API
|
||||
: new ForkTsCheckerWebpackPlugin({
|
||||
env.noTsCheck ?
|
||||
new webpack.DefinePlugin({}) // bogus plugin to satisfy webpack API
|
||||
:
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
checkSyntacticErrors: true,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'grafana.[name].[hash].css',
|
||||
filename: "grafana.[name].[hash].css"
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
||||
template: path.resolve(__dirname, '../../public/views/error-template.html'),
|
||||
inject: false,
|
||||
chunksSortMode: 'none',
|
||||
excludeChunks: ['dark', 'light']
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||
inject: 'body',
|
||||
chunks: ['manifest', 'vendor', 'app'],
|
||||
chunksSortMode: 'none',
|
||||
excludeChunks: ['dark', 'light']
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('development'),
|
||||
},
|
||||
'NODE_ENV': JSON.stringify('development')
|
||||
}
|
||||
}),
|
||||
// new BundleAnalyzerPlugin({
|
||||
// analyzerPort: 8889
|
||||
// })
|
||||
],
|
||||
]
|
||||
});
|
||||
|
@ -42,23 +42,24 @@ module.exports = merge(common, {
|
||||
|
||||
optimization: {
|
||||
removeAvailableModules: false,
|
||||
runtimeChunk: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
splitChunks: false
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
rules: [{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
use: [{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
babelrc: false,
|
||||
plugins: [
|
||||
[require('@rtsao/plugin-proposal-class-properties'), { loose: true }],
|
||||
[require('@rtsao/plugin-proposal-class-properties'), {
|
||||
loose: true
|
||||
}],
|
||||
'angularjs-annotate',
|
||||
'@babel/plugin-syntax-dynamic-import', // needed for `() => import()` in routes.ts
|
||||
'react-hot-loader/babel',
|
||||
@ -67,16 +68,18 @@ module.exports = merge(common, {
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: { browsers: 'last 3 versions' },
|
||||
targets: {
|
||||
browsers: 'last 3 versions'
|
||||
},
|
||||
useBuiltIns: 'entry',
|
||||
modules: false
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
'@babel/preset-react',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}, ],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
@ -86,7 +89,9 @@ module.exports = merge(common, {
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
config: { path: __dirname + '/postcss.config.js' },
|
||||
config: {
|
||||
path: __dirname + '/postcss.config.js'
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -108,6 +113,7 @@ module.exports = merge(common, {
|
||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||
inject: 'body',
|
||||
alwaysWriteToDisk: true,
|
||||
chunksSortMode: 'none'
|
||||
}),
|
||||
new HtmlWebpackHarddiskPlugin(),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
|
@ -20,8 +20,7 @@ module.exports = merge(common, {
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
rules: [{
|
||||
test: /\.tsx?$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
@ -44,11 +43,13 @@ module.exports = merge(common, {
|
||||
},
|
||||
},
|
||||
require('./sass.rule.js')({
|
||||
sourceMap: false, preserveUrl: false
|
||||
sourceMap: false,
|
||||
preserveUrl: false
|
||||
})
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
nodeEnv: 'production',
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: false,
|
||||
@ -70,12 +71,15 @@ module.exports = merge(common, {
|
||||
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
||||
template: path.resolve(__dirname, '../../public/views/error-template.html'),
|
||||
inject: false,
|
||||
excludeChunks: ['dark', 'light'],
|
||||
chunksSortMode: 'none'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||
inject: 'body',
|
||||
chunks: ['vendor', 'app'],
|
||||
excludeChunks: ['manifest', 'dark', 'light'],
|
||||
chunksSortMode: 'none'
|
||||
}),
|
||||
function () {
|
||||
this.hooks.done.tap('Done', function (stats) {
|
||||
|
Reference in New Issue
Block a user