mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
grafana/toolkit: Add support for extensions styling (#17938)
* Add support for static files * Use url-loader in single css setup * Add postcss setup * Expose emotion to plugins and externalise it in toolkit * Add readme note about emotion
This commit is contained in:
@@ -80,8 +80,8 @@ Adidtionaly, you can also provide additional Jest config via package.json file.
|
||||
- [`snapshotSerializers`](https://jest-bot.github.io/jest/docs/configuration.html#snapshotserializers-array-string)
|
||||
|
||||
|
||||
## Working with CSS
|
||||
We support pure css, SASS and CSS in JS approach (via Emotion).
|
||||
## Working with CSS & static assets
|
||||
We support pure css, SASS and CSS in JS approach (via Emotion). All static assets referenced in your code (i.e. images) should be placed under `src/static` directory and referenced using relative paths.
|
||||
|
||||
1. Single css/sass file
|
||||
Create your css/sass file and import it in your plugin entry point (typically module.ts):
|
||||
@@ -91,13 +91,37 @@ import 'path/to/your/css_or_sass
|
||||
```
|
||||
The styles will be injected via `style` tag during runtime.
|
||||
|
||||
2. Theme css/sass files
|
||||
If you want to provide different stylesheets for Dark/Light theme, create `dark.[css|scss]` and `light.[css|scss]` files in `src/styles` directory of your plugin. Based on that we will generate stylesheets that will end up in `dist/styles` directory.
|
||||
Note, that imported static assets will be inlined as base64 URIs. *This can be a subject of change in the future!*
|
||||
|
||||
2. Theme specific css/sass files
|
||||
If you want to provide different stylesheets for dark/light theme, create `dark.[css|scss]` and `light.[css|scss]` files in `src/styles` directory of your plugin. Based on that we will generate stylesheets that will end up in `dist/styles` directory.
|
||||
|
||||
TODO: add note about loadPluginCss
|
||||
|
||||
3. Emotion
|
||||
TODO
|
||||
Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables.
|
||||
|
||||
To use start using Emotion you first need to add it to your plugin dependencies:
|
||||
|
||||
```
|
||||
yarn add "@emotion/core"@10.0.14
|
||||
```
|
||||
|
||||
Then, import `css` function from emotion:
|
||||
|
||||
```import { css } from 'emotion'```
|
||||
|
||||
And start implementing your styles:
|
||||
|
||||
```tsx
|
||||
const MyComponent = () => {
|
||||
return <div className={css`background: red;`} />
|
||||
}
|
||||
```
|
||||
|
||||
Using themes: TODO, for now please refer to [internal guide](../../style_guides/themes.md)
|
||||
|
||||
> NOTE: We do not support Emotion's `css` prop. Use className instead!
|
||||
|
||||
## Prettier [todo]
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"copy-webpack-plugin": "5.0.3",
|
||||
"css-loader": "^3.0.0",
|
||||
"execa": "^1.0.0",
|
||||
"file-loader": "^4.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"html-loader": "0.5.5",
|
||||
"inquirer": "^6.3.1",
|
||||
@@ -49,6 +50,9 @@
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"ora": "^3.4.0",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-preset-env": "6.6.0",
|
||||
"prettier": "^1.17.1",
|
||||
"react-dev-utils": "^9.0.1",
|
||||
"replace-in-file": "^4.1.0",
|
||||
@@ -65,6 +69,7 @@
|
||||
"tslint": "5.14.0",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "3.5.1",
|
||||
"url-loader": "^2.0.1",
|
||||
"webpack": "4.35.0"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { getPluginJson, validatePluginJson } from './pluginValidation';
|
||||
|
||||
describe('pluginValdation', () => {
|
||||
describe('plugin.json', () => {
|
||||
test('missing plugin.json file', () => {
|
||||
expect(() => getPluginJson(`${__dirname}/mocks/missing-plugin-json`)).toThrow('plugin.json file is missing!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validatePluginJson', () => {
|
||||
test('missing plugin.json file', () => {
|
||||
expect(() => validatePluginJson({})).toThrow('Plugin id is missing in plugin.json');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import path = require('path');
|
||||
|
||||
interface PluginJSONSchema {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const validatePluginJson = (pluginJson: any) => {
|
||||
if (!pluginJson.id) {
|
||||
throw new Error('Plugin id is missing in plugin.json');
|
||||
}
|
||||
};
|
||||
|
||||
export const getPluginJson = (root: string = process.cwd()): PluginJSONSchema => {
|
||||
let pluginJson;
|
||||
|
||||
try {
|
||||
pluginJson = require(path.resolve(root, 'src/plugin.json'));
|
||||
} catch (e) {
|
||||
throw new Error('plugin.json file is missing!');
|
||||
}
|
||||
|
||||
validatePluginJson(pluginJson);
|
||||
|
||||
return pluginJson as PluginJSONSchema;
|
||||
};
|
||||
@@ -6,7 +6,7 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
import * as webpack from 'webpack';
|
||||
import { hasThemeStylesheets, getStyleLoaders, getStylesheetEntries } from './webpack/loaders';
|
||||
import { hasThemeStylesheets, getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
|
||||
|
||||
interface WebpackConfigurationOptions {
|
||||
watch?: boolean;
|
||||
@@ -82,8 +82,8 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => {
|
||||
{ from: '../LICENSE', to: '.' },
|
||||
{ from: 'img/*', to: '.' },
|
||||
{ from: '**/*.json', to: '.' },
|
||||
{ from: '**/*.svg', to: '.' },
|
||||
{ from: '**/*.png', to: '.' },
|
||||
// { from: '**/*.svg', to: '.' },
|
||||
// { from: '**/*.png', to: '.' },
|
||||
{ from: '**/*.html', to: '.' },
|
||||
],
|
||||
{ logLevel: options.watch ? 'silent' : 'warn' }
|
||||
@@ -131,6 +131,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
||||
filename: '[name].js',
|
||||
path: path.join(process.cwd(), 'dist'),
|
||||
libraryTarget: 'amd',
|
||||
publicPath: '/',
|
||||
},
|
||||
|
||||
performance: { hints: false },
|
||||
@@ -139,6 +140,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
||||
'jquery',
|
||||
'moment',
|
||||
'slate',
|
||||
'emotion',
|
||||
'prismjs',
|
||||
'slate-plain-serializer',
|
||||
'slate-react',
|
||||
@@ -146,6 +148,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
||||
'react-dom',
|
||||
'rxjs',
|
||||
'd3',
|
||||
'angular',
|
||||
'@grafana/ui',
|
||||
'@grafana/runtime',
|
||||
'@grafana/data',
|
||||
@@ -190,6 +193,7 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
|
||||
loader: 'html-loader',
|
||||
},
|
||||
},
|
||||
...getFileLoaders(),
|
||||
],
|
||||
},
|
||||
optimization,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { getPluginJson } from '../utils/pluginValidation';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
@@ -30,7 +33,7 @@ export const getStylesheetEntries = (root: string = process.cwd()) => {
|
||||
};
|
||||
|
||||
export const hasThemeStylesheets = (root: string = process.cwd()) => {
|
||||
const stylesheetsPaths = [`${root}/src/styles/light`, `${root}/src/styles/dark`];
|
||||
const stylesheetsPaths = getStylesheetPaths(root);
|
||||
const stylesheetsSummary: boolean[] = [];
|
||||
|
||||
const result = stylesheetsPaths.reduce((acc, current) => {
|
||||
@@ -68,25 +71,66 @@ export const getStyleLoaders = () => {
|
||||
const executiveLoader = shouldExtractCss
|
||||
? {
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
publicPath: '../',
|
||||
},
|
||||
}
|
||||
: 'style-loader';
|
||||
|
||||
const cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
const cssLoaders = [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
require('postcss-preset-env')({
|
||||
autoprefixer: { flexbox: 'no-2009', grid: true },
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [executiveLoader, cssLoader],
|
||||
use: [executiveLoader, ...cssLoaders],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [executiveLoader, cssLoader, 'sass-loader'],
|
||||
use: [executiveLoader, ...cssLoaders, 'sass-loader'],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getFileLoaders = () => {
|
||||
const shouldExtractCss = hasThemeStylesheets();
|
||||
// const pluginJson = getPluginJson();
|
||||
|
||||
return [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
use: [
|
||||
shouldExtractCss
|
||||
? {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
outputPath: 'static',
|
||||
name: '[name].[hash:8].[ext]',
|
||||
},
|
||||
}
|
||||
: // When using single css import images are inlined as base64 URIs in the result bundle
|
||||
{
|
||||
loader: 'url-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user