webpack.config.js 12 KB

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