webpack.config.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. const HtmlWebpackPlugin = require('html-webpack-plugin');
  2. const CopyPlugin = require("copy-webpack-plugin");
  3. const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");
  4. const { CleanWebpackPlugin } = require("clean-webpack-plugin");
  5. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  6. const CompressionPlugin = require("compression-webpack-plugin");
  7. const TerserPlugin = require("terser-webpack-plugin");
  8. const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  9. const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
  10. const webpack = require("webpack");
  11. const path = require("path");
  12. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  13. const globSync = require("glob").sync;
  14. const glob = require('glob');
  15. const { merge } = require('webpack-merge');
  16. const devserver = require('./webpack/webpack.dev.js');
  17. const fs = require('fs');
  18. const zlib = require("zlib");
  19. class BuildEventsHook {
  20. constructor(name, fn, stage = 'afterEmit') {
  21. this.name = name;
  22. this.stage = stage;
  23. this.function = fn;
  24. }
  25. apply(compiler) {
  26. compiler.hooks[this.stage].tap(this.name, this.function);
  27. }
  28. }
  29. module.exports = (env, options) => (
  30. merge(options.mode === "production" ? {} : devserver,
  31. {
  32. entry:
  33. {
  34. index: './src/index.ts'
  35. },
  36. devtool: "source-map",
  37. module: {
  38. rules: [
  39. {
  40. test: /\.ejs$/,
  41. loader: 'ejs-loader',
  42. options: {
  43. variable: 'data',
  44. interpolate : '\\{\\{(.+?)\\}\\}',
  45. evaluate : '\\[\\[(.+?)\\]\\]'
  46. }
  47. },
  48. {
  49. test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
  50. use: [
  51. {
  52. loader: 'file-loader',
  53. options: {
  54. name: '[name].[ext]',
  55. outputPath: 'fonts/'
  56. }
  57. }
  58. ]
  59. },
  60. {
  61. test: /\.scss$|\.css$/,
  62. use: [
  63. // options.mode !== "production"
  64. // ? "style-loader":
  65. {
  66. loader: MiniCssExtractPlugin.loader,
  67. options: {
  68. publicPath: "../",
  69. },
  70. },
  71. "css-loader",
  72. {
  73. loader: "postcss-loader",
  74. options: {
  75. postcssOptions: {
  76. plugins: [["autoprefixer"]],
  77. },
  78. },
  79. },
  80. "sass-loader",
  81. ],
  82. },
  83. {
  84. test: /\.js$/,
  85. exclude: /(node_modules|bower_components)/,
  86. use: {
  87. loader: "babel-loader",
  88. options: {
  89. presets: ["@babel/preset-env"],
  90. },
  91. },
  92. },
  93. {
  94. test: /\.(jpe?g|png|gif|svg)$/i,
  95. type: "asset",
  96. },
  97. // {
  98. // test: /\.html$/i,
  99. // type: "asset/resource",
  100. // },
  101. {
  102. test: /\.html$/i,
  103. loader: "html-loader",
  104. options: {
  105. minimize: true,
  106. }
  107. },
  108. // {
  109. // test: /\.html$/i,
  110. // use: [
  111. // "html-loader",
  112. // {
  113. // loader: 'markup-inline-loader',
  114. // options: {
  115. // svgo: {
  116. // plugins: [
  117. // {
  118. // removeTitle: true,
  119. // },
  120. // {
  121. // removeUselessStrokeAndFill: false,
  122. // },
  123. // {
  124. // removeUnknownsAndDefaults: false,
  125. // },
  126. // ],
  127. // },
  128. // },
  129. // },
  130. // ]
  131. // },
  132. {
  133. test: /\.tsx?$/,
  134. use: 'ts-loader',
  135. exclude: /node_modules/,
  136. },
  137. ],
  138. },
  139. plugins: [
  140. new HtmlWebpackPlugin({
  141. title: 'SqueezeESP32',
  142. template: './src/index.ejs',
  143. filename: 'index.html',
  144. inject: 'body',
  145. minify: {
  146. html5 : true,
  147. collapseWhitespace : true,
  148. minifyCSS : true,
  149. minifyJS : true,
  150. minifyURLs : false,
  151. removeAttributeQuotes : true,
  152. removeComments : true, // false for Vue SSR to find app placeholder
  153. removeEmptyAttributes : true,
  154. removeOptionalTags : true,
  155. removeRedundantAttributes : true,
  156. removeScriptTypeAttributes : true,
  157. removeStyleLinkTypeAttributese : true,
  158. useShortDoctype : true
  159. },
  160. favicon: "./src/assets/images/favicon-32x32.png",
  161. excludeChunks: ['test'],
  162. }),
  163. // new CompressionPlugin({
  164. // test: /\.(js|css|html|svg)$/,
  165. // //filename: '[path].br[query]',
  166. // filename: "[path][base].br",
  167. // algorithm: 'brotliCompress',
  168. // compressionOptions: { level: 11 },
  169. // threshold: 100,
  170. // minRatio: 0.8,
  171. // deleteOriginalAssets: false
  172. // }),
  173. new CompressionPlugin({
  174. //filename: '[path].gz[query]',
  175. test: /\.js$|\.css$|\.html$/,
  176. filename: "[path][base].gz",
  177. algorithm: 'gzip',
  178. threshold: 100,
  179. minRatio: 0.8,
  180. }),
  181. new MiniCssExtractPlugin({
  182. filename: "css/[name].[contenthash].css",
  183. }),
  184. new webpack.ProvidePlugin({
  185. $: "jquery",
  186. jQuery: "jquery",
  187. "window.jQuery": "jquery",
  188. Popper: ["popper.js", "default"],
  189. Util: "exports-loader?Util!bootstrap/js/dist/util",
  190. Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
  191. }),
  192. new BuildEventsHook('Update C App',
  193. function (stats, arguments) {
  194. if (options.mode !== "production") return;
  195. fs.appendFileSync('./dist/index.html.gz',
  196. zlib.gzipSync(fs.readFileSync('./dist/index.html'),
  197. {
  198. chunckSize: 65536,
  199. level: zlib.constants.Z_BEST_COMPRESSION
  200. }));
  201. var getDirectories = function (src, callback) {
  202. var searchPath = path.posix.relative(process.cwd(), path.posix.join(__dirname, src, '/**/*(*.gz|favicon-32x32.png)'));
  203. console.log(`Post build: Getting file list from ${searchPath}`);
  204. glob(searchPath, callback);
  205. };
  206. var cleanUpPath = path.posix.relative(process.cwd(), path.posix.join(__dirname, '../../../build/*.S'));
  207. console.log(`Post build: Cleaning up previous builds in ${cleanUpPath}`);
  208. glob(cleanUpPath, function (err, list) {
  209. if (err) {
  210. console.error('Error', err);
  211. } else {
  212. list.forEach(fileName => {
  213. try {
  214. console.log(`Post build: Purging old binary file ${fileName} from C project.`);
  215. fs.unlinkSync(fileName)
  216. //file removed
  217. } catch (ferr) {
  218. console.error(ferr)
  219. }
  220. });
  221. }
  222. },
  223. 'afterEmit'
  224. );
  225. console.log('Generating C include files from webpack build output');
  226. getDirectories('./dist', function (err, list) {
  227. if (err) {
  228. console.log('Error', err);
  229. } else {
  230. const regex = /^(.*\/)([^\/]*)$/
  231. const relativeRegex = /((\w+(?<!dist)\/){0,1}[^\/]*)$/
  232. let exportDefHead =
  233. `/***********************************
  234. webpack_headers
  235. ${arguments[1]}
  236. ***********************************/
  237. #pragma once
  238. #include <inttypes.h>
  239. extern const char * resource_lookups[];
  240. extern const uint8_t * resource_map_start[];
  241. extern const uint8_t * resource_map_end[];`;
  242. let exportDef = '// Automatically generated. Do not edit manually!.\n' +
  243. '#include <inttypes.h>\n';
  244. let lookupDef = 'const char * resource_lookups[] = {\n';
  245. let lookupMapStart = 'const uint8_t * resource_map_start[] = {\n';
  246. let lookupMapEnd = 'const uint8_t * resource_map_end[] = {\n';
  247. let cMake = '';
  248. list.push('./dist/index.html.gz');
  249. list.forEach(fileName => {
  250. let exportName = fileName.match(regex)[2].replace(/[\. \-]/gm, '_');
  251. let relativeName = fileName.match(relativeRegex)[1];
  252. exportDef += `extern const uint8_t _${exportName}_start[] asm("_binary_${exportName}_start");\nextern const uint8_t _${exportName}_end[] asm("_binary_${exportName}_end");\n`;
  253. lookupDef += '\t"/' + relativeName + '",\n';
  254. lookupMapStart += '\t_' + exportName + '_start,\n';
  255. lookupMapEnd += '\t_' + exportName + '_end,\n';
  256. cMake += `target_add_binary_data( __idf_wifi-manager ${path.posix.relative(path.posix.resolve(process.cwd(),'..','..'),fileName)
  257. } BINARY)\n`;
  258. });
  259. lookupDef += '""\n};\n';
  260. lookupMapStart = lookupMapStart.substring(0, lookupMapStart.length - 2) + '\n};\n';
  261. lookupMapEnd = lookupMapEnd.substring(0, lookupMapEnd.length - 2) + '\n};\n';
  262. try {
  263. fs.writeFileSync('webapp.cmake', cMake);
  264. fs.writeFileSync('webpack.c', exportDef + lookupDef + lookupMapStart + lookupMapEnd);
  265. fs.writeFileSync('webpack.h', exportDefHead);
  266. //file written successfully
  267. } catch (e) {
  268. console.error(e);
  269. }
  270. }
  271. });
  272. console.log('Post build completed.');
  273. })
  274. ],
  275. optimization: {
  276. minimize: true,
  277. minimizer: [
  278. new TerserPlugin(),
  279. new HtmlMinimizerPlugin(),
  280. new CssMinimizerPlugin(),
  281. new ImageMinimizerPlugin({
  282. minimizer: {
  283. implementation: ImageMinimizerPlugin.imageminMinify,
  284. options: {
  285. // Lossless optimization with custom option
  286. // Feel free to experiment with options for better result for you
  287. plugins: [
  288. ["gifsicle", { interlaced: true }],
  289. ["jpegtran", { progressive: true }],
  290. ["optipng", { optimizationLevel: 5 }],
  291. // Svgo configuration here https://github.com/svg/svgo#configuration
  292. [
  293. "svgo",
  294. {
  295. plugins: [
  296. {
  297. name: 'preset-default',
  298. params: {
  299. overrides: {
  300. // customize default plugin options
  301. inlineStyles: {
  302. onlyMatchedOnce: false,
  303. },
  304. // or disable plugins
  305. removeDoctype: false,
  306. },
  307. },
  308. }
  309. ],
  310. },
  311. ],
  312. ],
  313. },
  314. },
  315. }),
  316. //new BundleAnalyzerPlugin()
  317. ],
  318. // runtimeChunk: 'single',
  319. // splitChunks: {
  320. // chunks: 'all',
  321. // // maxInitialRequests: Infinity,
  322. // // minSize: 0,
  323. // cacheGroups: {
  324. // vendor: {
  325. // test: /node_modules/, // you may add "vendor.js" here if you want to
  326. // name: "node-modules",
  327. // chunks: "initial",
  328. // enforce: true
  329. // },
  330. // }
  331. // }
  332. },
  333. // output: {
  334. // filename: "[name].js",
  335. // path: path.resolve(__dirname, "dist"),
  336. // publicPath: "",
  337. // },
  338. resolve: {
  339. extensions: ['.tsx', '.ts', '.js', '.ejs' ],
  340. },
  341. output: {
  342. path: path.resolve(__dirname, 'dist'),
  343. filename: './js/[name].[fullhash:6].bundle.js',
  344. clean: true
  345. },
  346. }
  347. )
  348. );