webpack.plugin.config.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. const fs = require('fs');
  2. const path = require('path');
  3. const CopyWebpackPlugin = require('copy-webpack-plugin');
  4. const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
  5. const TerserPlugin = require('terser-webpack-plugin');
  6. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  7. const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
  8. import * as webpack from 'webpack';
  9. import { hasThemeStylesheets, getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
  10. interface WebpackConfigurationOptions {
  11. watch?: boolean;
  12. production?: boolean;
  13. }
  14. type WebpackConfigurationGetter = (options: WebpackConfigurationOptions) => webpack.Configuration;
  15. const findModuleTs = (base: string, files?: string[], result?: string[]) => {
  16. files = files || fs.readdirSync(base);
  17. result = result || [];
  18. if (files) {
  19. files.forEach(file => {
  20. const newbase = path.join(base, file);
  21. if (fs.statSync(newbase).isDirectory()) {
  22. result = findModuleTs(newbase, fs.readdirSync(newbase), result);
  23. } else {
  24. if (file.indexOf('module.ts') > -1) {
  25. // @ts-ignore
  26. result.push(newbase);
  27. }
  28. }
  29. });
  30. }
  31. return result;
  32. };
  33. const getModuleFiles = () => {
  34. return findModuleTs(path.resolve(process.cwd(), 'src'));
  35. };
  36. const getManualChunk = (id: string) => {
  37. if (id.endsWith('module.ts') || id.endsWith('module.tsx')) {
  38. const idx = id.indexOf('/src/');
  39. if (idx > 0) {
  40. const name = id.substring(idx + 5, id.lastIndexOf('.'));
  41. return {
  42. name,
  43. module: id,
  44. };
  45. }
  46. }
  47. };
  48. const getEntries = () => {
  49. const entries: { [key: string]: string } = {};
  50. const modules = getModuleFiles();
  51. modules.forEach(modFile => {
  52. const mod = getManualChunk(modFile);
  53. // @ts-ignore
  54. entries[mod.name] = mod.module;
  55. });
  56. return {
  57. ...entries,
  58. ...getStylesheetEntries(),
  59. };
  60. };
  61. const getCommonPlugins = (options: WebpackConfigurationOptions) => {
  62. const packageJson = require(path.resolve(process.cwd(), 'package.json'));
  63. return [
  64. new MiniCssExtractPlugin({
  65. // both options are optional
  66. filename: 'styles/[name].css',
  67. }),
  68. new webpack.optimize.OccurrenceOrderPlugin(true),
  69. new CopyWebpackPlugin(
  70. [
  71. { from: 'plugin.json', to: '.' },
  72. { from: '../README.md', to: '.' },
  73. { from: '../LICENSE', to: '.' },
  74. { from: 'img/*', to: '.' },
  75. { from: '**/*.json', to: '.' },
  76. // { from: '**/*.svg', to: '.' },
  77. // { from: '**/*.png', to: '.' },
  78. { from: '**/*.html', to: '.' },
  79. ],
  80. { logLevel: options.watch ? 'silent' : 'warn' }
  81. ),
  82. new ReplaceInFileWebpackPlugin([
  83. {
  84. dir: 'dist',
  85. files: ['plugin.json', 'README.md'],
  86. rules: [
  87. {
  88. search: '%VERSION%',
  89. replace: packageJson.version,
  90. },
  91. {
  92. search: '%TODAY%',
  93. replace: new Date().toISOString().substring(0, 10),
  94. },
  95. ],
  96. },
  97. ]),
  98. ];
  99. };
  100. export const getWebpackConfig: WebpackConfigurationGetter = options => {
  101. const plugins = getCommonPlugins(options);
  102. const optimization: { [key: string]: any } = {};
  103. if (options.production) {
  104. optimization.minimizer = [new TerserPlugin(), new OptimizeCssAssetsPlugin()];
  105. }
  106. return {
  107. mode: options.production ? 'production' : 'development',
  108. target: 'web',
  109. node: {
  110. fs: 'empty',
  111. net: 'empty',
  112. tls: 'empty',
  113. },
  114. context: path.join(process.cwd(), 'src'),
  115. devtool: 'source-map',
  116. entry: getEntries(),
  117. output: {
  118. filename: '[name].js',
  119. path: path.join(process.cwd(), 'dist'),
  120. libraryTarget: 'amd',
  121. publicPath: '/',
  122. },
  123. performance: { hints: false },
  124. externals: [
  125. 'lodash',
  126. 'jquery',
  127. 'moment',
  128. 'slate',
  129. 'emotion',
  130. 'prismjs',
  131. 'slate-plain-serializer',
  132. 'slate-react',
  133. 'react',
  134. 'react-dom',
  135. 'rxjs',
  136. 'd3',
  137. 'angular',
  138. '@grafana/ui',
  139. '@grafana/runtime',
  140. '@grafana/data',
  141. // @ts-ignore
  142. (context, request, callback) => {
  143. let prefix = 'app/';
  144. if (request.indexOf(prefix) === 0) {
  145. return callback(null, request);
  146. }
  147. prefix = 'grafana/';
  148. if (request.indexOf(prefix) === 0) {
  149. return callback(null, request.substr(prefix.length));
  150. }
  151. // @ts-ignore
  152. callback();
  153. },
  154. ],
  155. plugins,
  156. resolve: {
  157. extensions: ['.ts', '.tsx', '.js'],
  158. modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
  159. },
  160. module: {
  161. rules: [
  162. {
  163. test: /\.tsx?$/,
  164. loaders: [
  165. {
  166. loader: 'babel-loader',
  167. options: { presets: ['@babel/preset-env'] },
  168. },
  169. 'ts-loader',
  170. ],
  171. exclude: /(node_modules)/,
  172. },
  173. ...getStyleLoaders(),
  174. {
  175. test: /\.html$/,
  176. exclude: [/node_modules/],
  177. use: {
  178. loader: 'html-loader',
  179. },
  180. },
  181. ...getFileLoaders(),
  182. ],
  183. },
  184. optimization,
  185. // optimization: {
  186. // splitChunks: {
  187. // chunks: 'all',
  188. // name: 'shared'
  189. // }
  190. // }
  191. };
  192. };