mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/toolkit: Add option to override webpack config (#20872)
* Toolkit: Add possibility to add custom webpack config * Toolkit: Refactor webpack to utilize async-await * Toolkit: Rename config file and allow named export
This commit is contained in:
parent
34aaa3d1f7
commit
f1845d8084
@ -127,6 +127,27 @@ Currently we support following Jest configuration properties:
|
|||||||
- [`snapshotSerializers`](https://jest-bot.github.io/jest/docs/configuration.html#snapshotserializers-array-string)
|
- [`snapshotSerializers`](https://jest-bot.github.io/jest/docs/configuration.html#snapshotserializers-array-string)
|
||||||
- [`moduleNameMapper`](https://jestjs.io/docs/en/configuration#modulenamemapper-object-string-string)
|
- [`moduleNameMapper`](https://jestjs.io/docs/en/configuration#modulenamemapper-object-string-string)
|
||||||
|
|
||||||
|
### How can I customize Webpack rules or plugins?
|
||||||
|
You can provide your own webpack configuration.
|
||||||
|
Provide a function implementing `CustomWebpackConfigurationGetter` in a file named `webpack.config.ts`.
|
||||||
|
|
||||||
|
You can import the correct interface and Options from `@grafana/toolkit/src/config`.
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
``` ts
|
||||||
|
import { CustomWebpackConfigurationGetter } from '@grafana/toolkit/src/config'
|
||||||
|
import CustomPlugin from 'custom-plugin';
|
||||||
|
|
||||||
|
const getWebpackConfig: CustomWebpackConfigurationGetter = (defaultConfig, options) => {
|
||||||
|
console.log('Custom config');
|
||||||
|
defaultConfig.plugins.push(new CustomPlugin())
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export = getWebpackConfig;
|
||||||
|
```
|
||||||
|
|
||||||
### How can I style my plugin?
|
### How can I style my plugin?
|
||||||
We support pure CSS, SASS, and CSS-in-JS approach (via [Emotion](https://emotion.sh/)).
|
We support pure CSS, SASS, and CSS-in-JS approach (via [Emotion](https://emotion.sh/)).
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import webpack = require('webpack');
|
import webpack = require('webpack');
|
||||||
import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||||
import clearConsole = require('react-dev-utils/clearConsole');
|
import clearConsole = require('react-dev-utils/clearConsole');
|
||||||
import { getWebpackConfig } from '../../../config/webpack.plugin.config';
|
import { loadWebpackConfig } from '../../../config/webpack.plugin.config';
|
||||||
|
|
||||||
export interface PluginBundleOptions {
|
export interface PluginBundleOptions {
|
||||||
watch: boolean;
|
watch: boolean;
|
||||||
@ -12,7 +12,7 @@ export interface PluginBundleOptions {
|
|||||||
// export const bundlePlugin = useSpinner<PluginBundleOptions>('Bundle plugin', ({ watch }) => {
|
// export const bundlePlugin = useSpinner<PluginBundleOptions>('Bundle plugin', ({ watch }) => {
|
||||||
export const bundlePlugin = async ({ watch, production }: PluginBundleOptions) => {
|
export const bundlePlugin = async ({ watch, production }: PluginBundleOptions) => {
|
||||||
const compiler = webpack(
|
const compiler = webpack(
|
||||||
getWebpackConfig({
|
await loadWebpackConfig({
|
||||||
watch,
|
watch,
|
||||||
production,
|
production,
|
||||||
})
|
})
|
||||||
|
1
packages/grafana-toolkit/src/config/index.ts
Normal file
1
packages/grafana-toolkit/src/config/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { CustomWebpackConfigurationGetter, WebpackConfigurationOptions } from './webpack.plugin.config';
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "Testversion"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "Testversion"
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { CustomWebpackConfigurationGetter } from '../../../webpack.plugin.config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const overrideWebpackConfig: CustomWebpackConfigurationGetter = (originalConfig, options) => {
|
||||||
|
const config = _.cloneDeep(originalConfig);
|
||||||
|
config.name = 'customConfig';
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
export = overrideWebpackConfig;
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "Testversion"
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { CustomWebpackConfigurationGetter } from '../../../webpack.plugin.config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export const getWebpackConfig: CustomWebpackConfigurationGetter = (originalConfig, options) => {
|
||||||
|
const config = _.cloneDeep(originalConfig);
|
||||||
|
config.name = 'customConfig';
|
||||||
|
return config;
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "Testversion"
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
/* WRONG CONFIG ON PURPOSE - DO NOT COPY THIS */
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
name: 'test',
|
||||||
|
};
|
||||||
|
|
||||||
|
export = config;
|
@ -1,7 +1,12 @@
|
|||||||
import { findModuleFiles } from './webpack.plugin.config';
|
import { findModuleFiles, loadWebpackConfig } from './webpack.plugin.config';
|
||||||
const fs = require('fs');
|
import fs from 'fs';
|
||||||
|
import * as webpackConfig from './webpack.plugin.config';
|
||||||
|
|
||||||
jest.mock('fs');
|
jest.mock('./webpack/loaders', () => ({
|
||||||
|
getFileLoaders: () => [],
|
||||||
|
getStylesheetEntries: () => [],
|
||||||
|
getStyleLoaders: () => [],
|
||||||
|
}));
|
||||||
|
|
||||||
const modulePathsMock = [
|
const modulePathsMock = [
|
||||||
'some/path/module.ts',
|
'some/path/module.ts',
|
||||||
@ -15,16 +20,56 @@ const modulePathsMock = [
|
|||||||
describe('Plugin webpack config', () => {
|
describe('Plugin webpack config', () => {
|
||||||
describe('findModuleTs', () => {
|
describe('findModuleTs', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
fs.statSync.mockReturnValue({
|
jest.spyOn(fs, 'statSync').mockReturnValue({
|
||||||
isDirectory: () => false,
|
isDirectory: () => false,
|
||||||
});
|
} as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('finds module.ts and module.tsx files', () => {
|
afterAll(() => {
|
||||||
const moduleFiles = findModuleFiles('/', modulePathsMock);
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds module.ts and module.tsx files', async () => {
|
||||||
|
const moduleFiles = await findModuleFiles('/', modulePathsMock);
|
||||||
expect(moduleFiles.length).toBe(2);
|
expect(moduleFiles.length).toBe(2);
|
||||||
// normalize windows path - \\ -> /
|
// normalize windows path - \\ -> /
|
||||||
expect(moduleFiles.map(p => p.replace(/\\/g, '/'))).toEqual(['/some/path/module.ts', '/some/path/module.tsx']);
|
expect(moduleFiles.map(p => p.replace(/\\/g, '/'))).toEqual(['/some/path/module.ts', '/some/path/module.tsx']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loadWebpackConfig', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.spyOn(webpackConfig, 'findModuleFiles').mockReturnValue(new Promise((res, _) => res([])));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses default config if no override exists', async () => {
|
||||||
|
const spy = jest.spyOn(process, 'cwd');
|
||||||
|
spy.mockReturnValue(`${__dirname}/mocks/webpack/noOverride/`);
|
||||||
|
await loadWebpackConfig({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls customConfig if it exists', async () => {
|
||||||
|
const spy = jest.spyOn(process, 'cwd');
|
||||||
|
spy.mockReturnValue(`${__dirname}/mocks/webpack/overrides/`);
|
||||||
|
const config = await loadWebpackConfig({});
|
||||||
|
expect(config.name).toBe('customConfig');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads export named getWebpackConfiguration', async () => {
|
||||||
|
const spy = jest.spyOn(process, 'cwd');
|
||||||
|
spy.mockReturnValue(`${__dirname}/mocks/webpack/overridesNamedExport/`);
|
||||||
|
const config = await loadWebpackConfig({});
|
||||||
|
expect(config.name).toBe('customConfig');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if module does not export function', async () => {
|
||||||
|
const spy = jest.spyOn(process, 'cwd');
|
||||||
|
spy.mockReturnValue(`${__dirname}/mocks/webpack/unsupportedOverride/`);
|
||||||
|
await expect(loadWebpackConfig({})).rejects.toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const util = require('util');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
|
const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
|
||||||
@ -7,24 +8,31 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
const readdirPromise = util.promisify(fs.readdir);
|
||||||
|
const accessPromise = util.promisify(fs.access);
|
||||||
|
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
|
import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
|
||||||
|
|
||||||
interface WebpackConfigurationOptions {
|
export interface WebpackConfigurationOptions {
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
production?: boolean;
|
production?: boolean;
|
||||||
}
|
}
|
||||||
type WebpackConfigurationGetter = (options: WebpackConfigurationOptions) => webpack.Configuration;
|
type WebpackConfigurationGetter = (options: WebpackConfigurationOptions) => Promise<webpack.Configuration>;
|
||||||
|
export type CustomWebpackConfigurationGetter = (
|
||||||
|
originalConfig: webpack.Configuration,
|
||||||
|
options: WebpackConfigurationOptions
|
||||||
|
) => webpack.Configuration;
|
||||||
|
|
||||||
export const findModuleFiles = (base: string, files?: string[], result?: string[]) => {
|
export const findModuleFiles = async (base: string, files?: string[], result?: string[]) => {
|
||||||
files = files || fs.readdirSync(base);
|
files = files || (await readdirPromise(base));
|
||||||
result = result || [];
|
result = result || [];
|
||||||
|
|
||||||
if (files) {
|
if (files) {
|
||||||
files.forEach(file => {
|
files.forEach(async file => {
|
||||||
const newbase = path.join(base, file);
|
const newbase = path.join(base, file);
|
||||||
if (fs.statSync(newbase).isDirectory()) {
|
if (fs.statSync(newbase).isDirectory()) {
|
||||||
result = findModuleFiles(newbase, fs.readdirSync(newbase), result);
|
result = await findModuleFiles(newbase, await readdirPromise(newbase), result);
|
||||||
} else {
|
} else {
|
||||||
const filename = path.basename(file);
|
const filename = path.basename(file);
|
||||||
if (/^module.(t|j)sx?$/.exec(filename)) {
|
if (/^module.(t|j)sx?$/.exec(filename)) {
|
||||||
@ -56,9 +64,9 @@ const getManualChunk = (id: string) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntries = () => {
|
const getEntries = async () => {
|
||||||
const entries: { [key: string]: string } = {};
|
const entries: { [key: string]: string } = {};
|
||||||
const modules = getModuleFiles();
|
const modules = await getModuleFiles();
|
||||||
|
|
||||||
modules.forEach(modFile => {
|
modules.forEach(modFile => {
|
||||||
const mod = getManualChunk(modFile);
|
const mod = getManualChunk(modFile);
|
||||||
@ -114,7 +122,7 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
const getBaseWebpackConfig: WebpackConfigurationGetter = async options => {
|
||||||
const plugins = getCommonPlugins(options);
|
const plugins = getCommonPlugins(options);
|
||||||
const optimization: { [key: string]: any } = {};
|
const optimization: { [key: string]: any } = {};
|
||||||
|
|
||||||
@ -134,7 +142,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
|||||||
},
|
},
|
||||||
context: path.join(process.cwd(), 'src'),
|
context: path.join(process.cwd(), 'src'),
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: getEntries(),
|
entry: await getEntries(),
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
path: path.join(process.cwd(), 'dist'),
|
path: path.join(process.cwd(), 'dist'),
|
||||||
@ -224,3 +232,26 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
|||||||
optimization,
|
optimization,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadWebpackConfig: WebpackConfigurationGetter = async options => {
|
||||||
|
const baseConfig = await getBaseWebpackConfig(options);
|
||||||
|
const customWebpackPath = path.resolve(process.cwd(), 'webpack.config.ts');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accessPromise(customWebpackPath);
|
||||||
|
const customConfig = require(customWebpackPath);
|
||||||
|
const configGetter = customConfig.getWebpackConfig || customConfig;
|
||||||
|
if (typeof configGetter !== 'function') {
|
||||||
|
throw Error(
|
||||||
|
'Custom webpack config needs to export a function implementing CustomWebpackConfigurationGetter. Function needs to be ' +
|
||||||
|
'module export or named "getWebpackConfig"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (configGetter as CustomWebpackConfigurationGetter)(baseConfig, options);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
return baseConfig;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user