mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Build: Enable long term caching for frontend assets (#47625)
* build(webpack): move CopyUniconsPlugin into own file * chore(webpack): delete unused blobUrl and compile loaders * build(webpack): prefer contenthash over fullhash for longer caching * build(webpack): set optimization.moduleIds named only in dev * build(webpack): introduce HTMLWebpackCSSChunks so templates can access theme css by name * feat: inject css files with contenthash in html templates * revert(error-template): remove ContentDeliveryURL from CSS href * refactor(index-template): update grafanaBootData.themePaths * chore(webpack): add typescript annotations for CopyUniconsPlugin
This commit is contained in:
parent
20a83ba14f
commit
78bef7a26a
@ -10,7 +10,11 @@
|
|||||||
|
|
||||||
<base href="[[.AppSubUrl]]/" />
|
<base href="[[.AppSubUrl]]/" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="public/build/grafana.[[ .Theme ]].<%= compilation.hash %>.css" />
|
[[ if eq .Theme "light" ]]
|
||||||
|
<link rel="stylesheet" href="public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>" />
|
||||||
|
[[ else ]]
|
||||||
|
<link rel="stylesheet" href="public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>" />
|
||||||
|
[[ end ]]
|
||||||
|
|
||||||
<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" />
|
||||||
|
@ -20,10 +20,12 @@
|
|||||||
<link rel="icon" type="image/png" href="[[.FavIcon]]" />
|
<link rel="icon" type="image/png" href="[[.FavIcon]]" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="[[.AppleTouchIcon]]" />
|
<link rel="apple-touch-icon" sizes="180x180" href="[[.AppleTouchIcon]]" />
|
||||||
<link rel="mask-icon" href="[[.ContentDeliveryURL]]public/img/grafana_mask_icon.svg" color="#F05A28" />
|
<link rel="mask-icon" href="[[.ContentDeliveryURL]]public/img/grafana_mask_icon.svg" color="#F05A28" />
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
[[ if eq .Theme "light" ]]
|
||||||
href="[[.ContentDeliveryURL]]public/build/grafana.[[ .Theme ]].<%= compilation.hash %>.css"
|
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>" />
|
||||||
/>
|
[[ else ]]
|
||||||
|
<link rel="stylesheet" href="[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>" />
|
||||||
|
[[ end ]]
|
||||||
|
|
||||||
<script nonce="[[.Nonce]]">
|
<script nonce="[[.Nonce]]">
|
||||||
performance.mark('frontend_boot_css_time_seconds');
|
performance.mark('frontend_boot_css_time_seconds');
|
||||||
@ -251,8 +253,8 @@
|
|||||||
settings: [[.Settings]],
|
settings: [[.Settings]],
|
||||||
navTree: [[.NavTree]],
|
navTree: [[.NavTree]],
|
||||||
themePaths: {
|
themePaths: {
|
||||||
light: '[[.ContentDeliveryURL]]public/build/grafana.light.<%= compilation.hash %>.css',
|
light: '[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.light %>',
|
||||||
dark: '[[.ContentDeliveryURL]]public/build/grafana.dark.<%= compilation.hash %>.css'
|
dark: '[[.ContentDeliveryURL]]public/build/<%= htmlWebpackPlugin.files.cssChunks.dark %>'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
const loaderUtils = require('loader-utils');
|
|
||||||
|
|
||||||
module.exports = function blobUrl(source) {
|
|
||||||
const { type } = loaderUtils.getOptions(this) || {};
|
|
||||||
return `module.exports = URL.createObjectURL(new Blob([${JSON.stringify(source)}]${
|
|
||||||
type ? `, { type: ${JSON.stringify(type)} }` : ''
|
|
||||||
}));`;
|
|
||||||
};
|
|
@ -1,100 +0,0 @@
|
|||||||
const loaderUtils = require('loader-utils');
|
|
||||||
const ExternalsPlugin = require('webpack/lib/ExternalsPlugin');
|
|
||||||
const LoaderTargetPlugin = require('webpack/lib/LoaderTargetPlugin');
|
|
||||||
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
|
|
||||||
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
|
|
||||||
const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin');
|
|
||||||
|
|
||||||
const COMPILATION_METADATA = Symbol('COMPILATION_METADATA');
|
|
||||||
|
|
||||||
module.exports.COMPILATION_METADATA = COMPILATION_METADATA;
|
|
||||||
|
|
||||||
module.exports.pitch = function pitch(remainingRequest) {
|
|
||||||
const { target, plugins = [], output, emit } = loaderUtils.getOptions(this) || {};
|
|
||||||
|
|
||||||
if (target !== 'worker') {
|
|
||||||
throw new Error(`Unsupported compile target: ${JSON.stringify(target)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cacheable(false);
|
|
||||||
|
|
||||||
const { filename, options = {} } = getOutputFilename(output, { target });
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
const currentCompilation = this._compilation;
|
|
||||||
|
|
||||||
const outputFilename = loaderUtils.interpolateName(this, filename, {
|
|
||||||
context: options.context || currentCompilation.options.context,
|
|
||||||
regExp: options.regExp,
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputOptions = {
|
|
||||||
filename: outputFilename,
|
|
||||||
chunkFilename: `${outputFilename}.[id]`,
|
|
||||||
namedChunkFilename: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const compilerOptions = currentCompilation.compiler.options;
|
|
||||||
const childCompiler = currentCompilation.createChildCompiler('worker', outputOptions, [
|
|
||||||
// https://github.com/webpack/webpack/blob/master/lib/WebpackOptionsApply.js
|
|
||||||
new WebWorkerTemplatePlugin(outputOptions),
|
|
||||||
new LoaderTargetPlugin('webworker'),
|
|
||||||
...(this.target === 'web' || this.target === 'webworker' ? [] : [new NodeTargetPlugin()]),
|
|
||||||
|
|
||||||
// https://github.com/webpack-contrib/worker-loader/issues/95#issuecomment-352856617
|
|
||||||
...(compilerOptions.externals ? [new ExternalsPlugin(compilerOptions.externals)] : []),
|
|
||||||
|
|
||||||
...plugins,
|
|
||||||
|
|
||||||
new SingleEntryPlugin(this.context, `!!${remainingRequest}`, 'main'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const subCache = `subcache ${__dirname} ${remainingRequest}`;
|
|
||||||
|
|
||||||
childCompiler.plugin('compilation', (compilation) => {
|
|
||||||
if (!compilation.cache) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(subCache in compilation.cache)) {
|
|
||||||
Object.assign(compilation.cache, { [subCache]: {} });
|
|
||||||
}
|
|
||||||
Object.assign(compilation, { cache: compilation.cache[subCache] });
|
|
||||||
});
|
|
||||||
|
|
||||||
const callback = this.async();
|
|
||||||
|
|
||||||
childCompiler.runAsChild((error, entries, compilation) => {
|
|
||||||
if (error) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
if (entries.length === 0) {
|
|
||||||
return callback(null, null);
|
|
||||||
}
|
|
||||||
const mainFilename = entries[0].files[0];
|
|
||||||
if (emit === false) {
|
|
||||||
delete currentCompilation.assets[mainFilename];
|
|
||||||
}
|
|
||||||
callback(null, compilation.assets[mainFilename].source(), null, {
|
|
||||||
[COMPILATION_METADATA]: entries[0].files,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function getOutputFilename(options, { target }) {
|
|
||||||
if (!options) {
|
|
||||||
return { filename: `[fullhash].${target}.js`, options: undefined };
|
|
||||||
}
|
|
||||||
if (typeof options === 'string') {
|
|
||||||
return { filename: options, options: undefined };
|
|
||||||
}
|
|
||||||
if (typeof options === 'object') {
|
|
||||||
return {
|
|
||||||
filename: options.filename,
|
|
||||||
options: {
|
|
||||||
context: options.context,
|
|
||||||
regExp: options.regExp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid compile output options: ${options}`);
|
|
||||||
}
|
|
39
scripts/webpack/plugins/CopyUniconsPlugin.js
Normal file
39
scripts/webpack/plugins/CopyUniconsPlugin.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
class CopyUniconsPlugin {
|
||||||
|
/**
|
||||||
|
* @param {import('webpack').Compiler} compiler
|
||||||
|
*/
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.afterEnvironment.tap(
|
||||||
|
'CopyUniconsPlugin',
|
||||||
|
/**
|
||||||
|
* @param {import('webpack').Compilation} compilation
|
||||||
|
*/
|
||||||
|
() => {
|
||||||
|
let destDir = path.resolve(__dirname, '../../../public/img/icons/unicons');
|
||||||
|
|
||||||
|
if (!fs.pathExistsSync(destDir)) {
|
||||||
|
let srcDir = path.join(
|
||||||
|
path.dirname(require.resolve('iconscout-unicons-tarball/package.json')),
|
||||||
|
'unicons/svg/line'
|
||||||
|
);
|
||||||
|
fs.copySync(srcDir, destDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
let solidDestDir = path.resolve(__dirname, '../../../public/img/icons/solid');
|
||||||
|
|
||||||
|
if (!fs.pathExistsSync(solidDestDir)) {
|
||||||
|
let srcDir = path.join(
|
||||||
|
path.dirname(require.resolve('iconscout-unicons-tarball/package.json')),
|
||||||
|
'unicons/svg/solid'
|
||||||
|
);
|
||||||
|
fs.copySync(srcDir, solidDestDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CopyUniconsPlugin;
|
42
scripts/webpack/plugins/HTMLWebpackCSSChunks.js
Normal file
42
scripts/webpack/plugins/HTMLWebpackCSSChunks.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin returns the css associated with entrypoints. Those chunks can be found
|
||||||
|
* in `htmlWebpackPlugin.files.cssChunks`.
|
||||||
|
* The HTML Webpack plugin removed the chunks object in v5 in favour of an array however if we want
|
||||||
|
* to do anything smart with hashing (e.g. [contenthash]) we need a map of { themeName: chunkNameWithHash }.
|
||||||
|
*/
|
||||||
|
class HTMLWebpackCSSChunks {
|
||||||
|
/**
|
||||||
|
* @param {import('webpack').Compiler} compiler
|
||||||
|
*/
|
||||||
|
apply(compiler) {
|
||||||
|
compiler.hooks.compilation.tap(
|
||||||
|
'HTMLWebpackCSSChunks',
|
||||||
|
/**
|
||||||
|
* @param {import('webpack').Compilation} compilation
|
||||||
|
*/
|
||||||
|
(compilation) => {
|
||||||
|
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
|
||||||
|
'HTMLWebpackCSSChunks',
|
||||||
|
(data, cb) => {
|
||||||
|
data.assets.cssChunks = {};
|
||||||
|
|
||||||
|
for (const entryPoint of compilation.entrypoints.values()) {
|
||||||
|
for (const chunk of entryPoint.chunks) {
|
||||||
|
const cssFile = [...chunk.files].find((file) => file.endsWith('.css'));
|
||||||
|
if (cssFile !== undefined) {
|
||||||
|
data.assets.cssChunks[chunk.name] = cssFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HTMLWebpackCSSChunks;
|
@ -1,36 +1,10 @@
|
|||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const fs = require('fs-extra');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
const CopyUniconsPlugin = require('./plugins/CopyUniconsPlugin');
|
||||||
const CorsWorkerPlugin = require('./plugins/CorsWorkerPlugin');
|
const CorsWorkerPlugin = require('./plugins/CorsWorkerPlugin');
|
||||||
|
|
||||||
class CopyUniconsPlugin {
|
|
||||||
apply(compiler) {
|
|
||||||
compiler.hooks.afterEnvironment.tap('CopyUniconsPlugin', () => {
|
|
||||||
let destDir = path.resolve(__dirname, '../../public/img/icons/unicons');
|
|
||||||
|
|
||||||
if (!fs.pathExistsSync(destDir)) {
|
|
||||||
let srcDir = path.join(
|
|
||||||
path.dirname(require.resolve('iconscout-unicons-tarball/package.json')),
|
|
||||||
'unicons/svg/line'
|
|
||||||
);
|
|
||||||
fs.copySync(srcDir, destDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
let solidDestDir = path.resolve(__dirname, '../../public/img/icons/solid');
|
|
||||||
|
|
||||||
if (!fs.pathExistsSync(solidDestDir)) {
|
|
||||||
let srcDir = path.join(
|
|
||||||
path.dirname(require.resolve('iconscout-unicons-tarball/package.json')),
|
|
||||||
'unicons/svg/solid'
|
|
||||||
);
|
|
||||||
fs.copySync(srcDir, solidDestDir);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'web',
|
target: 'web',
|
||||||
entry: {
|
entry: {
|
||||||
@ -39,16 +13,14 @@ module.exports = {
|
|||||||
output: {
|
output: {
|
||||||
clean: true,
|
clean: true,
|
||||||
path: path.resolve(__dirname, '../../public/build'),
|
path: path.resolve(__dirname, '../../public/build'),
|
||||||
filename: '[name].[fullhash].js',
|
filename: '[name].[contenthash].js',
|
||||||
// Keep publicPath relative for host.com/grafana/ deployments
|
// Keep publicPath relative for host.com/grafana/ deployments
|
||||||
publicPath: 'public/build/',
|
publicPath: 'public/build/',
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
|
extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'],
|
||||||
alias: {
|
alias: {
|
||||||
// storybook v6 bump caused the app to bundle multiple versions of react breaking hooks
|
// some of data source plugins use global Prism object to add the language definition
|
||||||
// make sure to resolve only from the project: https://github.com/facebook/react/issues/13991#issuecomment-435587809
|
|
||||||
// some of data source pluginis use global Prism object to add the language definition
|
|
||||||
// we want to have same Prism object in core and in grafana/ui
|
// we want to have same Prism object in core and in grafana/ui
|
||||||
prismjs: require.resolve('prismjs'),
|
prismjs: require.resolve('prismjs'),
|
||||||
},
|
},
|
||||||
@ -134,13 +106,12 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
|
test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: { name: 'static/img/[name].[hash:8].[ext]' },
|
options: { name: 'static/img/[name].[contenthash:8].[ext]' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 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: 'named',
|
|
||||||
runtimeChunk: 'single',
|
runtimeChunk: 'single',
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
|
@ -8,6 +8,7 @@ const path = require('path');
|
|||||||
const { DefinePlugin } = require('webpack');
|
const { DefinePlugin } = require('webpack');
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
|
||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
|
||||||
@ -51,11 +52,11 @@ module.exports = (env = {}) =>
|
|||||||
// https://webpack.js.org/guides/build-performance/#output-without-path-info
|
// https://webpack.js.org/guides/build-performance/#output-without-path-info
|
||||||
output: {
|
output: {
|
||||||
pathinfo: false,
|
pathinfo: false,
|
||||||
filename: '[name].js',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://webpack.js.org/guides/build-performance/#avoid-extra-optimization-steps
|
// https://webpack.js.org/guides/build-performance/#avoid-extra-optimization-steps
|
||||||
optimization: {
|
optimization: {
|
||||||
|
moduleIds: 'named',
|
||||||
runtimeChunk: true,
|
runtimeChunk: true,
|
||||||
removeAvailableModules: false,
|
removeAvailableModules: false,
|
||||||
removeEmptyChunks: false,
|
removeEmptyChunks: false,
|
||||||
@ -91,7 +92,7 @@ module.exports = (env = {}) =>
|
|||||||
extensions: ['.ts', '.tsx'],
|
extensions: ['.ts', '.tsx'],
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'grafana.[name].[fullhash].css',
|
filename: 'grafana.[name].[contenthash].css',
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
||||||
@ -103,11 +104,11 @@ module.exports = (env = {}) =>
|
|||||||
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'),
|
||||||
hash: true,
|
|
||||||
inject: false,
|
inject: false,
|
||||||
chunksSortMode: 'none',
|
chunksSortMode: 'none',
|
||||||
excludeChunks: ['dark', 'light'],
|
excludeChunks: ['dark', 'light'],
|
||||||
}),
|
}),
|
||||||
|
new HTMLWebpackCSSChunks(),
|
||||||
new DefinePlugin({
|
new DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
NODE_ENV: JSON.stringify('development'),
|
NODE_ENV: JSON.stringify('development'),
|
||||||
|
@ -7,6 +7,7 @@ const path = require('path');
|
|||||||
const { DefinePlugin } = require('webpack');
|
const { DefinePlugin } = require('webpack');
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
|
||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
module.exports = merge(common, {
|
module.exports = merge(common, {
|
||||||
@ -77,16 +78,16 @@ module.exports = merge(common, {
|
|||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'grafana.[name].[fullhash].css',
|
filename: 'grafana.[name].[contenthash].css',
|
||||||
}),
|
}),
|
||||||
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'),
|
||||||
hash: true,
|
|
||||||
inject: false,
|
inject: false,
|
||||||
chunksSortMode: 'none',
|
chunksSortMode: 'none',
|
||||||
excludeChunks: ['dark', 'light'],
|
excludeChunks: ['dark', 'light'],
|
||||||
}),
|
}),
|
||||||
|
new HTMLWebpackCSSChunks(),
|
||||||
new ReactRefreshWebpackPlugin(),
|
new ReactRefreshWebpackPlugin(),
|
||||||
new DefinePlugin({
|
new DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
@ -7,6 +7,7 @@ const path = require('path');
|
|||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
const { merge } = require('webpack-merge');
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const HTMLWebpackCSSChunks = require('./plugins/HTMLWebpackCSSChunks');
|
||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
module.exports = (env = {}) =>
|
module.exports = (env = {}) =>
|
||||||
@ -63,7 +64,7 @@ module.exports = (env = {}) =>
|
|||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'grafana.[name].[fullhash].css',
|
filename: 'grafana.[name].[contenthash].css',
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
filename: path.resolve(__dirname, '../../public/views/error.html'),
|
||||||
@ -79,6 +80,7 @@ module.exports = (env = {}) =>
|
|||||||
excludeChunks: ['manifest', 'dark', 'light'],
|
excludeChunks: ['manifest', 'dark', 'light'],
|
||||||
chunksSortMode: 'none',
|
chunksSortMode: 'none',
|
||||||
}),
|
}),
|
||||||
|
new HTMLWebpackCSSChunks(),
|
||||||
function () {
|
function () {
|
||||||
this.hooks.done.tap('Done', function (stats) {
|
this.hooks.done.tap('Done', function (stats) {
|
||||||
if (stats.compilation.errors && stats.compilation.errors.length) {
|
if (stats.compilation.errors && stats.compilation.errors.length) {
|
||||||
|
Loading…
Reference in New Issue
Block a user