webpack.config.js 14 KB

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