From 7761245d087c9477ea7f3e50c60daecd6cc6ec14 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Sun, 28 Jun 2020 04:59:56 +0200
Subject: [PATCH] Move fomantic and jQuery to main webpack bundle (#11997)

This saves around 3 MB binary size by not including useless fomantic
files in the build. Also, this allows us to move jQuery into the main
bundle as well which eliminates a few HTTP requests.

Also included are webpack config changes:

- split less and css loaders to speed up compliation
- enable css sourcemaps
- switch css minfier plugin to cssnano-webpack-plugin which works better
  for sourcemaps than the previous plugin

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
---
 .gitignore                 |  2 +-
 Makefile                   | 10 ++---
 modules/public/public.go   |  1 -
 package-lock.json          | 28 +++++-------
 package.json               |  4 +-
 semantic.json              | 10 ++---
 templates/base/footer.tmpl |  9 ++--
 templates/base/head.tmpl   |  1 -
 webpack.config.js          | 92 ++++++++++++++++++++++++++------------
 9 files changed, 88 insertions(+), 69 deletions(-)

diff --git a/.gitignore b/.gitignore
index 90a103eca11cb..b5929cd12a63b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,8 +79,8 @@ coverage.all
 /public/serviceworker.js
 /public/css
 /public/fonts
-/public/fomantic
 /public/img/svg
+/web_src/fomantic/build
 /VERSION
 
 # Snapcraft
diff --git a/Makefile b/Makefile
index 37af184945ae6..8da5c8fad1743 100644
--- a/Makefile
+++ b/Makefile
@@ -88,7 +88,7 @@ LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(G
 
 GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list -mod=vendor ./... | grep -v /vendor/)))
 
-WEBPACK_SOURCES := $(shell find web_src/js web_src/less -type f)
+WEBPACK_SOURCES := $(shell find web_src -type f)
 WEBPACK_CONFIGS := webpack.config.js
 WEBPACK_DEST := public/js/index.js public/css/index.css
 WEBPACK_DEST_ENTRIES := public/js public/css public/fonts public/serviceworker.js public/img/svg
@@ -111,8 +111,8 @@ endif
 GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES))
 
 FOMANTIC_CONFIGS := semantic.json web_src/fomantic/theme.config.less web_src/fomantic/_site/globals/site.variables
-FOMANTIC_DEST := public/fomantic/semantic.min.js public/fomantic/semantic.min.css
-FOMANTIC_DEST_DIR := public/fomantic
+FOMANTIC_DEST := web_src/fomantic/build/semantic.js web_src/fomantic/build/semantic.css
+FOMANTIC_DEST_DIR := web_src/fomantic/build
 
 #To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.20.1
 SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger
@@ -297,7 +297,7 @@ lint-frontend: node_modules
 	npx stylelint web_src/less
 
 .PHONY: watch-frontend
-watch-frontend: node_modules
+watch-frontend: node-check $(FOMANTIC_DEST) node_modules
 	rm -rf $(WEBPACK_DEST_ENTRIES)
 	NODE_ENV=development npx webpack --hide-modules --display-entrypoints=false --watch --progress
 
@@ -590,7 +590,7 @@ npm-update: node-check | node_modules
 .PHONY: fomantic
 fomantic: $(FOMANTIC_DEST)
 
-$(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) package-lock.json | node_modules
+$(FOMANTIC_DEST): $(FOMANTIC_CONFIGS) | node_modules
 	rm -rf $(FOMANTIC_DEST_DIR)
 	cp web_src/fomantic/theme.config.less node_modules/fomantic-ui/src/theme.config
 	cp -r web_src/fomantic/_site/* node_modules/fomantic-ui/src/_site/
diff --git a/modules/public/public.go b/modules/public/public.go
index 8d027855c2eee..9532c9b366816 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -33,7 +33,6 @@ type Options struct {
 // KnownPublicEntries list all direct children in the `public` directory
 var KnownPublicEntries = []string{
 	"css",
-	"fomantic",
 	"img",
 	"js",
 	"serviceworker.js",
diff --git a/package-lock.json b/package-lock.json
index 13edbf798bba1..f43eeb0a1acef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3458,6 +3458,15 @@
       "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz",
       "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q=="
     },
+    "cssnano-webpack-plugin": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/cssnano-webpack-plugin/-/cssnano-webpack-plugin-1.0.3.tgz",
+      "integrity": "sha512-OkxkOR6WlTBdIbPObCTWuUuZCr3bDPYClm5gquo2LEkOCvM1mbI9MAUstqS2+u6pImnd2f6dNgmmcvVbmYJ7fg==",
+      "requires": {
+        "cssnano": "^4.1.10",
+        "webpack-sources": "^1.4.3"
+      }
+    },
     "csso": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz",
@@ -7897,15 +7906,6 @@
       "integrity": "sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA==",
       "dev": true
     },
-    "last-call-webpack-plugin": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz",
-      "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==",
-      "requires": {
-        "lodash": "^4.17.5",
-        "webpack-sources": "^1.1.0"
-      }
-    },
     "last-run": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz",
@@ -9617,15 +9617,6 @@
         "mimic-fn": "^1.0.0"
       }
     },
-    "optimize-css-assets-webpack-plugin": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz",
-      "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==",
-      "requires": {
-        "cssnano": "^4.1.10",
-        "last-call-webpack-plugin": "^3.0.0"
-      }
-    },
     "optionator": {
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -10932,6 +10923,7 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz",
       "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==",
+      "dev": true,
       "requires": {
         "postcss": "^7.0.26"
       }
diff --git a/package.json b/package.json
index 862f16616acf5..3e656e9cfa2d2 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
     "clipboard": "2.0.6",
     "core-js": "3.6.5",
     "css-loader": "3.5.3",
-    "cssnano": "4.1.10",
+    "cssnano-webpack-plugin": "1.0.3",
     "domino": "2.1.5",
     "dropzone": "5.7.1",
     "fast-glob": "3.2.2",
@@ -29,10 +29,8 @@
     "mini-css-extract-plugin": "0.9.0",
     "monaco-editor": "0.20.0",
     "monaco-editor-webpack-plugin": "1.9.0",
-    "optimize-css-assets-webpack-plugin": "5.0.3",
     "postcss-loader": "3.0.0",
     "postcss-preset-env": "6.7.0",
-    "postcss-safe-parser": "4.0.2",
     "svg-sprite-loader": "5.0.0",
     "svgo": "1.3.2",
     "svgo-loader": "2.2.1",
diff --git a/semantic.json b/semantic.json
index 0187b9c68fe04..a103afccb3bec 100644
--- a/semantic.json
+++ b/semantic.json
@@ -9,12 +9,12 @@
       "themes": "src/themes/"
     },
     "output": {
-      "packaged": "../../public/fomantic/",
-      "uncompressed": "../../public/fomantic/components/",
-      "compressed": "../../public/fomantic/components/",
-      "themes": "../../public/fomantic/themes/"
+      "packaged": "../../web_src/fomantic/build/",
+      "uncompressed": "../../web_src/fomantic/build/components/",
+      "compressed": "../../web_src/fomantic/build/components/",
+      "themes": "../../web_src/fomantic/build/themes/"
     },
-    "clean": "../../public/fomantic/"
+    "clean": "../../web_src/fomantic/build/"
   },
   "permission": false,
   "autoInstall": false,
diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl
index 6570a6eaed46a..e7d8c46f88748 100644
--- a/templates/base/footer.tmpl
+++ b/templates/base/footer.tmpl
@@ -11,8 +11,6 @@
 	{{template "custom/body_outer_post" .}}
 
 	{{template "base/footer_content" .}}
-
-	<script src="{{StaticUrlPrefix}}/js/jquery.js?v={{MD5 AppVer}}"></script>
 {{if .RequireSimpleMDE}}
 	<script src="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.js"></script>
 	<script src="{{StaticUrlPrefix}}/vendor/plugins/codemirror/addon/mode/loadmode.js"></script>
@@ -23,9 +21,6 @@
 {{end}}
 
 <!-- Third-party libraries -->
-{{if .RequireMinicolors}}
-	<script src="{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js"></script>
-{{end}}
 {{if .RequireU2F}}
 	<script src="{{StaticUrlPrefix}}/vendor/plugins/u2f/index.js"></script>
 {{end}}
@@ -34,8 +29,10 @@
 		<script src='{{ URLJoin .RecaptchaURL "api.js"}}' async></script>
 	{{end}}
 {{end}}
-	<script src="{{StaticUrlPrefix}}/fomantic/semantic.min.js?v={{MD5 AppVer}}"></script>
 	<script src="{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}"></script>
+{{if .RequireMinicolors}}
+	<script src="{{StaticUrlPrefix}}/vendor/plugins/jquery.minicolors/jquery.minicolors.min.js"></script>
+{{end}}
 {{template "custom/footer" .}}
 </body>
 </html>
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl
index 0f575233aa2d9..7999eebb11216 100644
--- a/templates/base/head.tmpl
+++ b/templates/base/head.tmpl
@@ -95,7 +95,6 @@
 {{if .RequireSimpleMDE}}
 	<link rel="stylesheet" href="{{StaticUrlPrefix}}/vendor/plugins/simplemde/simplemde.min.css">
 {{end}}
-	<link rel="stylesheet" href="{{StaticUrlPrefix}}/fomantic/semantic.min.css?v={{MD5 AppVer}}">
 	<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/index.css?v={{MD5 AppVer}}">
 	<noscript>
 		<style>
diff --git a/webpack.config.js b/webpack.config.js
index 09caeb40292ac..fa96f42e9133e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,12 +1,10 @@
-const cssnano = require('cssnano');
 const fastGlob = require('fast-glob');
 const wrapAnsi = require('wrap-ansi');
+const CssNanoPlugin = require('cssnano-webpack-plugin');
 const FixStyleOnlyEntriesPlugin = require('webpack-fix-style-only-entries');
 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
-const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
 const PostCSSPresetEnv = require('postcss-preset-env');
-const PostCSSSafeParser = require('postcss-safe-parser');
 const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
 const TerserPlugin = require('terser-webpack-plugin');
 const VueLoaderPlugin = require('vue-loader/lib/plugin');
@@ -24,19 +22,31 @@ for (const path of glob('web_src/less/themes/*.less')) {
 
 const isProduction = process.env.NODE_ENV !== 'development';
 
+const filterCssImport = (parsedImport, cssFile) => {
+  const url = parsedImport && parsedImport.url ? parsedImport.url : parsedImport;
+  const importedFile = url.replace(/[?#].+/, '').toLowerCase();
+  if (cssFile.includes('monaco')) return true;
+  if (cssFile.includes('fomantic')) {
+    if (/brand-icons/.test(importedFile)) return false;
+    if (/(eot|ttf|woff)$/.test(importedFile)) return false;
+    return true;
+  }
+  return cssFile.includes('node_modules');
+};
+
 module.exports = {
   mode: isProduction ? 'production' : 'development',
   entry: {
     index: [
+      resolve(__dirname, 'web_src/js/jquery.js'),
+      resolve(__dirname, 'web_src/fomantic/build/semantic.js'),
       resolve(__dirname, 'web_src/js/index.js'),
+      resolve(__dirname, 'web_src/fomantic/build/semantic.css'),
       resolve(__dirname, 'web_src/less/index.less'),
     ],
     swagger: [
       resolve(__dirname, 'web_src/js/standalone/swagger.js'),
     ],
-    jquery: [
-      resolve(__dirname, 'web_src/js/jquery.js'),
-    ],
     serviceworker: [
       resolve(__dirname, 'web_src/js/serviceworker.js'),
     ],
@@ -66,12 +76,9 @@ module.exports = {
           },
         },
       }),
-      new OptimizeCSSAssetsPlugin({
-        cssProcessor: cssnano,
-        cssProcessorOptions: {
-          parser: PostCSSSafeParser,
-        },
-        cssProcessorPluginOptions: {
+      new CssNanoPlugin({
+        sourceMap: true,
+        cssnanoOptions: {
           preset: [
             'default',
             {
@@ -91,10 +98,10 @@ module.exports = {
         monaco: {
           test: /monaco-editor/,
           name: 'monaco',
-          chunks: 'async'
-        }
-      }
-    }
+          chunks: 'async',
+        },
+      },
+    },
   },
   module: {
     rules: [
@@ -150,12 +157,41 @@ module.exports = {
                 ],
                 '@babel/plugin-proposal-object-rest-spread',
               ],
+              generatorOpts: {
+                compact: false,
+              },
             },
           },
         ],
       },
       {
-        test: /\.(less|css)$/i,
+        test: /.css$/i,
+        use: [
+          {
+            loader: MiniCssExtractPlugin.loader,
+          },
+          {
+            loader: 'css-loader',
+            options: {
+              importLoaders: 1,
+              url: filterCssImport,
+              import: filterCssImport,
+              sourceMap: true,
+            },
+          },
+          {
+            loader: 'postcss-loader',
+            options: {
+              plugins: () => [
+                PostCSSPresetEnv(),
+              ],
+              sourceMap: true,
+            },
+          },
+        ],
+      },
+      {
+        test: /.less$/i,
         use: [
           {
             loader: MiniCssExtractPlugin.loader,
@@ -164,11 +200,10 @@ module.exports = {
             loader: 'css-loader',
             options: {
               importLoaders: 2,
-              url: (_url, resourcePath) => {
-                // only resolve URLs for dependencies
-                return resourcePath.includes('node_modules');
-              },
-            }
+              url: filterCssImport,
+              import: filterCssImport,
+              sourceMap: true,
+            },
           },
           {
             loader: 'postcss-loader',
@@ -176,10 +211,14 @@ module.exports = {
               plugins: () => [
                 PostCSSPresetEnv(),
               ],
+              sourceMap: true,
             },
           },
           {
             loader: 'less-loader',
+            options: {
+              sourceMap: true,
+            },
           },
         ],
       },
@@ -232,9 +271,10 @@ module.exports = {
       chunkFilename: 'css/[name].css',
     }),
     new SourceMapDevToolPlugin({
-      filename: 'js/[name].js.map',
+      filename: '[file].map',
       include: [
         'js/index.js',
+        'css/index.css',
       ],
     }),
     new SpriteLoaderPlugin({
@@ -251,12 +291,6 @@ module.exports = {
       modulesDirectories: [
         resolve(__dirname, 'node_modules'),
       ],
-      additionalModules: [
-        {
-          name: 'fomantic-ui',
-          directory: resolve(__dirname, 'node_modules/fomantic-ui'),
-        },
-      ],
       renderLicenses: (modules) => {
         const line = '-'.repeat(80);
         return modules.map((module) => {