Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

341 lines
9.5 KiB

8 years ago
8 years ago
6 years ago
8 years ago
8 years ago
8 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
5 years ago
5 years ago
  1. /*
  2. ESP8266 file system builder
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. /*eslint quotes: ['error', 'single']*/
  16. /*eslint-env es6*/
  17. // -----------------------------------------------------------------------------
  18. // Dependencies
  19. // -----------------------------------------------------------------------------
  20. const path = require('path');
  21. const gulp = require('gulp');
  22. const through = require('through2');
  23. const htmllint = require('gulp-htmllint');
  24. const csslint = require('gulp-csslint');
  25. const htmlmin = require('html-minifier');
  26. const gzip = require('gulp-gzip');
  27. const inline = require('gulp-inline-source-html');
  28. const rename = require('gulp-rename');
  29. const replace = require('gulp-replace');
  30. // -----------------------------------------------------------------------------
  31. // Configuration
  32. // -----------------------------------------------------------------------------
  33. const htmlFolder = 'html/';
  34. const dataFolder = 'espurna/data/';
  35. const staticFolder = 'espurna/static/';
  36. // -----------------------------------------------------------------------------
  37. // Methods
  38. // -----------------------------------------------------------------------------
  39. var toMinifiedHtml = function(options) {
  40. return through.obj(function (source, encoding, callback) {
  41. if (source.isNull()) {
  42. callback(null, source);
  43. return;
  44. }
  45. var contents = source.contents.toString();
  46. source.contents = Buffer.from(htmlmin.minify(contents, options));
  47. callback(null, source);
  48. });
  49. }
  50. var toHeader = function(name, debug) {
  51. return through.obj(function (source, encoding, callback) {
  52. var parts = source.path.split(path.sep);
  53. var filename = parts[parts.length - 1];
  54. var safename = name || filename.split('.').join('_');
  55. // Generate output
  56. var output = '';
  57. output += '#define ' + safename + '_len ' + source.contents.length + '\n';
  58. output += 'const uint8_t ' + safename + '[] PROGMEM = {';
  59. for (var i=0; i<source.contents.length; i++) {
  60. if (i > 0) { output += ','; }
  61. if (0 === (i % 20)) { output += '\n'; }
  62. output += '0x' + ('00' + source.contents[i].toString(16)).slice(-2);
  63. }
  64. output += '\n};';
  65. // clone the contents
  66. var destination = source.clone();
  67. destination.path = source.path + '.h';
  68. destination.contents = Buffer.from(output);
  69. if (debug) {
  70. console.info('Image ' + filename + ' \tsize: ' + source.contents.length + ' bytes');
  71. }
  72. callback(null, destination);
  73. });
  74. };
  75. var htmllintReporter = function(filepath, issues) {
  76. if (issues.length > 0) {
  77. issues.forEach(function (issue) {
  78. console.info(
  79. '[gulp-htmllint] ' +
  80. filepath + ' [' +
  81. issue.line + ',' +
  82. issue.column + ']: ' +
  83. '(' + issue.code + ') ' +
  84. issue.msg
  85. );
  86. });
  87. process.exitCode = 1;
  88. }
  89. };
  90. // TODO: this is a roughly equivalent port of the gulp-remove-code,
  91. // which also uses regexp rules to filter in-between specially-formatted comment blocks
  92. var jsRegexp = function(module) {
  93. return '//\\s*removeIf\\(!' + module + '\\)'
  94. + '\\s*(\n|\r|.)*?'
  95. + '//\\s*endRemoveIf\\(!' + module + '\\)';
  96. }
  97. var cssRegexp = function(module) {
  98. return '/\\*\\s*removeIf\\(!' + module + '\\)\\s*\\*/'
  99. + '\\s*(\n|\r|.)*?'
  100. + '/\\*\\s*endRemoveIf\\(!' + module + '\\)\\s*\\*/';
  101. }
  102. var htmlRegexp = function(module) {
  103. return '<!--\\s*removeIf\\(!' + module + '\\)\\s*-->'
  104. + '\\s*(\n|\r|.)*?'
  105. + '<!--\\s*endRemoveIf\\(!' + module + '\\)\\s*-->';
  106. }
  107. var generateRegexps = function(modules, func) {
  108. var regexps = new Set();
  109. for (const [module, enabled] of Object.entries(modules)) {
  110. if (enabled) {
  111. continue;
  112. }
  113. const expression = func(module);
  114. const re = new RegExp(expression, 'gm');
  115. regexps.add(re);
  116. }
  117. return regexps;
  118. }
  119. // TODO: use html parser here?
  120. // TODO: separate js files to include js, html & css and avoid 2 step regexp?
  121. var htmlRemover = function(modules) {
  122. const regexps = generateRegexps(modules, htmlRegexp);
  123. return through.obj(function (source, _, callback) {
  124. if (source.isNull()) {
  125. callback(null, source);
  126. return;
  127. }
  128. var contents = source.contents.toString();
  129. for (var regexp of regexps) {
  130. contents = contents.replace(regexp, '');
  131. }
  132. source.contents = Buffer.from(contents);
  133. callback(null, source);
  134. });
  135. }
  136. var inlineHandler = function(modules) {
  137. return function(source) {
  138. if (((source.sourcepath === 'custom.css') || (source.sourcepath === 'custom.js'))) {
  139. const filter = (source.type === 'css') ? cssRegexp : jsRegexp;
  140. const regexps = generateRegexps(modules, filter);
  141. var content = source.fileContent;
  142. for (var regexp of regexps) {
  143. content = content.replace(regexp, '');
  144. }
  145. source.fileContent = content;
  146. return;
  147. }
  148. if (source.content) {
  149. return;
  150. }
  151. // Just ignore the vendored libs, repackaging makes things worse for the size
  152. const path = source.sourcepath;
  153. if (path.endsWith('.min.js')) {
  154. source.compress = false;
  155. } else if (path.endsWith('.min.css')) {
  156. source.compress = false;
  157. }
  158. };
  159. }
  160. var buildWebUI = function(module) {
  161. // Declare some modules as optional to remove with
  162. // removeIf(!name) ...code... endRemoveIf(!name) sections
  163. // (via gulp-remove-code)
  164. var modules = {
  165. 'light': false,
  166. 'sensor': false,
  167. 'rfbridge': false,
  168. 'rfm69': false,
  169. 'garland': false,
  170. 'thermostat': false,
  171. 'lightfox': false,
  172. 'curtain': false
  173. };
  174. // Note: only build these when specified as module arg
  175. var excludeAll = [
  176. 'rfm69',
  177. 'lightfox'
  178. ];
  179. // 'all' to include all *but* excludeAll
  180. // '<module>' to include a single module
  181. // 'small' is the default state (all disabled)
  182. if ('all' === module) {
  183. Object.keys(modules).
  184. filter(function(key) {
  185. return excludeAll.indexOf(key) < 0;
  186. }).
  187. forEach(function(key) {
  188. modules[key] = true;
  189. });
  190. } else if ('small' !== module) {
  191. modules[module] = true;
  192. }
  193. return gulp.src(htmlFolder + '*.html').
  194. pipe(htmllint({
  195. 'failOnError': true,
  196. 'rules': {
  197. 'id-class-style': false,
  198. 'label-req-for': false,
  199. 'line-end-style': false,
  200. 'attr-req-value': false
  201. }
  202. }, htmllintReporter)).
  203. pipe(htmlRemover(modules)).
  204. pipe(inline({handlers: [inlineHandler(modules)]})).
  205. pipe(toMinifiedHtml({
  206. collapseWhitespace: true,
  207. removeComments: true,
  208. minifyCSS: false,
  209. minifyJS: false
  210. })).
  211. pipe(replace('pure-', 'p-')).
  212. pipe(gzip({ gzipOptions: { level: 9 } })).
  213. pipe(rename('index.' + module + '.html.gz')).
  214. pipe(gulp.dest(dataFolder)).
  215. pipe(toHeader('webui_image', true)).
  216. pipe(gulp.dest(staticFolder));
  217. };
  218. // -----------------------------------------------------------------------------
  219. // Tasks
  220. // -----------------------------------------------------------------------------
  221. gulp.task('certs', function() {
  222. gulp.src(dataFolder + 'server.*').
  223. pipe(toHeader('', false)).
  224. pipe(gulp.dest(staticFolder));
  225. });
  226. gulp.task('csslint', function() {
  227. gulp.src(htmlFolder + '*.css').
  228. pipe(csslint({ids: false})).
  229. pipe(csslint.formatter());
  230. });
  231. gulp.task('webui_small', function() {
  232. return buildWebUI('small');
  233. });
  234. gulp.task('webui_sensor', function() {
  235. return buildWebUI('sensor');
  236. });
  237. gulp.task('webui_light', function() {
  238. return buildWebUI('light');
  239. });
  240. gulp.task('webui_rfbridge', function() {
  241. return buildWebUI('rfbridge');
  242. });
  243. gulp.task('webui_rfm69', function() {
  244. return buildWebUI('rfm69');
  245. });
  246. gulp.task('webui_lightfox', function() {
  247. return buildWebUI('lightfox');
  248. });
  249. gulp.task('webui_garland', function() {
  250. return buildWebUI('garland');
  251. });
  252. gulp.task('webui_thermostat', function() {
  253. return buildWebUI('thermostat');
  254. });
  255. gulp.task('webui_curtain', function() {
  256. return buildWebUI('curtain');
  257. });
  258. gulp.task('webui_all', function() {
  259. return buildWebUI('all');
  260. });
  261. gulp.task('webui',
  262. gulp.parallel(
  263. 'webui_small',
  264. 'webui_sensor',
  265. 'webui_light',
  266. 'webui_rfbridge',
  267. 'webui_rfm69',
  268. 'webui_lightfox',
  269. 'webui_garland',
  270. 'webui_thermostat',
  271. 'webui_curtain',
  272. 'webui_all'
  273. )
  274. );
  275. gulp.task('default', gulp.series('webui'));