Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/frontend/startup_css')
-rw-r--r--scripts/frontend/startup_css/clean_css.js83
-rw-r--r--scripts/frontend/startup_css/constants.js106
-rw-r--r--scripts/frontend/startup_css/get_css_path.js22
-rw-r--r--scripts/frontend/startup_css/get_startup_css.js69
-rw-r--r--scripts/frontend/startup_css/main.js60
-rwxr-xr-xscripts/frontend/startup_css/setup.sh76
-rwxr-xr-xscripts/frontend/startup_css/startup_css_changed.sh40
-rw-r--r--scripts/frontend/startup_css/utils.js8
-rw-r--r--scripts/frontend/startup_css/write_startup_scss.js28
9 files changed, 492 insertions, 0 deletions
diff --git a/scripts/frontend/startup_css/clean_css.js b/scripts/frontend/startup_css/clean_css.js
new file mode 100644
index 00000000000..67a0453e816
--- /dev/null
+++ b/scripts/frontend/startup_css/clean_css.js
@@ -0,0 +1,83 @@
+const { memoize, isString, isRegExp } = require('lodash');
+const { parse } = require('postcss');
+const { CSS_TO_REMOVE } = require('./constants');
+
+const getSelectorRemoveTesters = memoize(() =>
+ CSS_TO_REMOVE.map((x) => {
+ if (isString(x)) {
+ return (selector) => x === selector;
+ }
+ if (isRegExp(x)) {
+ return (selector) => x.test(selector);
+ }
+
+ throw new Error(`Unexpected type in CSS_TO_REMOVE content "${x}". Expected String or RegExp.`);
+ }),
+);
+
+const getRemoveTesters = memoize(() => {
+ const selectorTesters = getSelectorRemoveTesters();
+
+ // These are mostly carried over from the previous project
+ // https://gitlab.com/gitlab-org/frontend/gitlab-css-statistics/-/blob/2aa00af25dba08fc71081c77206f45efe817ea4b/lib/gl_startup_extract.js
+ return [
+ (node) => node.type === 'comment',
+ (node) =>
+ node.type === 'atrule' &&
+ (node.params === 'print' ||
+ node.params === 'prefers-reduced-motion: reduce' ||
+ node.name === 'keyframe' ||
+ node.name === 'charset'),
+ (node) => node.selector && node.selectors && !node.selectors.length,
+ (node) => node.selector && selectorTesters.some((fn) => fn(node.selector)),
+ (node) =>
+ node.type === 'decl' &&
+ (node.prop === 'transition' ||
+ node.prop.indexOf('-webkit-') > -1 ||
+ node.prop.indexOf('-ms-') > -1),
+ ];
+});
+
+const getNodesToRemove = (nodes) => {
+ const removeTesters = getRemoveTesters();
+ const remNodes = [];
+
+ nodes.forEach((node) => {
+ if (removeTesters.some((fn) => fn(node))) {
+ remNodes.push(node);
+ } else if (node.nodes?.length) {
+ remNodes.push(...getNodesToRemove(node.nodes));
+ }
+ });
+
+ return remNodes;
+};
+
+const getEmptyNodesToRemove = (nodes) =>
+ nodes
+ .filter((node) => node.nodes)
+ .reduce((acc, node) => {
+ if (node.nodes.length) {
+ acc.push(...getEmptyNodesToRemove(node.nodes));
+ } else {
+ acc.push(node);
+ }
+
+ return acc;
+ }, []);
+
+const cleanCSS = (css) => {
+ const cssRoot = parse(css);
+
+ getNodesToRemove(cssRoot.nodes).forEach((node) => {
+ node.remove();
+ });
+
+ getEmptyNodesToRemove(cssRoot.nodes).forEach((node) => {
+ node.remove();
+ });
+
+ return cssRoot.toResult().css;
+};
+
+module.exports = { cleanCSS };
diff --git a/scripts/frontend/startup_css/constants.js b/scripts/frontend/startup_css/constants.js
new file mode 100644
index 00000000000..5f6189d9e59
--- /dev/null
+++ b/scripts/frontend/startup_css/constants.js
@@ -0,0 +1,106 @@
+const path = require('path');
+const IS_EE = require('../../../config/helpers/is_ee_env');
+
+// controls --------------------------------------------------------------------
+const HTML_TO_REMOVE = [
+ 'style',
+ 'script',
+ 'link[rel="stylesheet"]',
+ '.content-wrapper',
+ '#js-peek',
+ '.modal',
+ '.feature-highlight',
+ // The user has to open up the responsive nav, so we don't need it on load
+ '.top-nav-responsive',
+ // We don't want to capture all the children of a dropdown-menu
+ '.dropdown-menu',
+];
+const CSS_TO_REMOVE = [
+ '.tooltip',
+ '.tooltip.show',
+ '.fa',
+ '.gl-accessibility:focus',
+ '.toasted-container',
+ 'body .toasted-container.bottom-left',
+ '.popover',
+ '.with-performance-bar .navbar-gitlab',
+ '.text-secondary',
+ /\.feature-highlight-popover-content/,
+ /\.commit/,
+ /\.md/,
+ /\.with-performance-bar/,
+];
+const APPLICATION_CSS_PREFIX = 'application';
+const APPLICATION_DARK_CSS_PREFIX = 'application_dark';
+const UTILITIES_CSS_PREFIX = 'application_utilities';
+const UTILITIES_DARK_CSS_PREFIX = 'application_utilities_dark';
+
+// paths -----------------------------------------------------------------------
+const ROOT = path.resolve(__dirname, '../../..');
+const ROOT_RAILS = IS_EE ? path.join(ROOT, 'ee') : ROOT;
+const FIXTURES_FOLDER_NAME = IS_EE ? 'fixtures-ee' : 'fixtures';
+const FIXTURES_ROOT = path.join(ROOT, 'tmp/tests/frontend', FIXTURES_FOLDER_NAME);
+const PATH_SIGNIN_HTML = path.join(FIXTURES_ROOT, 'startup_css/sign-in.html');
+const PATH_ASSETS = path.join(ROOT, 'tmp/startup_css_assets');
+const PATH_STARTUP_SCSS = path.join(ROOT_RAILS, 'app/assets/stylesheets/startup');
+
+// helpers ---------------------------------------------------------------------
+const createMainOutput = ({ outFile, cssKeys, type }) => ({
+ outFile,
+ htmlPaths: [
+ path.join(FIXTURES_ROOT, `startup_css/project-${type}.html`),
+ path.join(FIXTURES_ROOT, `startup_css/project-${type}-legacy-menu.html`),
+ path.join(FIXTURES_ROOT, `startup_css/project-${type}-legacy-sidebar.html`),
+ path.join(FIXTURES_ROOT, `startup_css/project-${type}-signed-out.html`),
+ ],
+ cssKeys,
+ purgeOptions: {
+ safelist: {
+ standard: [
+ 'page-with-icon-sidebar',
+ 'sidebar-collapsed-desktop',
+ // We want to include the root dropdown-menu style since it should be hidden by default
+ 'dropdown-menu',
+ ],
+ // We want to include the identicon backgrounds
+ greedy: [/^bg[0-9]$/],
+ },
+ },
+});
+
+const OUTPUTS = [
+ createMainOutput({
+ type: 'general',
+ outFile: 'startup-general',
+ cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
+ }),
+ createMainOutput({
+ type: 'dark',
+ outFile: 'startup-dark',
+ cssKeys: [APPLICATION_DARK_CSS_PREFIX, UTILITIES_DARK_CSS_PREFIX],
+ }),
+ {
+ outFile: 'startup-signin',
+ htmlPaths: [PATH_SIGNIN_HTML],
+ cssKeys: [APPLICATION_CSS_PREFIX, UTILITIES_CSS_PREFIX],
+ purgeOptions: {
+ safelist: {
+ standard: ['fieldset', 'hidden'],
+ deep: [/login-page$/],
+ },
+ },
+ },
+];
+
+module.exports = {
+ HTML_TO_REMOVE,
+ CSS_TO_REMOVE,
+ APPLICATION_CSS_PREFIX,
+ APPLICATION_DARK_CSS_PREFIX,
+ UTILITIES_CSS_PREFIX,
+ UTILITIES_DARK_CSS_PREFIX,
+ ROOT,
+ PATH_ASSETS,
+ PATH_STARTUP_SCSS,
+ OUTPUTS,
+};
diff --git a/scripts/frontend/startup_css/get_css_path.js b/scripts/frontend/startup_css/get_css_path.js
new file mode 100644
index 00000000000..54078cf3149
--- /dev/null
+++ b/scripts/frontend/startup_css/get_css_path.js
@@ -0,0 +1,22 @@
+const fs = require('fs');
+const path = require('path');
+const { memoize } = require('lodash');
+const { PATH_ASSETS } = require('./constants');
+const { die } = require('./utils');
+
+const listAssetsDir = memoize(() => fs.readdirSync(PATH_ASSETS));
+
+const getCSSPath = (prefix) => {
+ const matcher = new RegExp(`^${prefix}-[^-]+\\.css$`);
+ const cssPath = listAssetsDir().find((x) => matcher.test(x));
+
+ if (!cssPath) {
+ die(
+ `Could not find the CSS asset matching "${prefix}". Have you run "scripts/frontend/startup_css/setup.sh"?`,
+ );
+ }
+
+ return path.join(PATH_ASSETS, cssPath);
+};
+
+module.exports = { getCSSPath };
diff --git a/scripts/frontend/startup_css/get_startup_css.js b/scripts/frontend/startup_css/get_startup_css.js
new file mode 100644
index 00000000000..10e8371df8c
--- /dev/null
+++ b/scripts/frontend/startup_css/get_startup_css.js
@@ -0,0 +1,69 @@
+const fs = require('fs');
+const cheerio = require('cheerio');
+const { mergeWith, isArray } = require('lodash');
+const { PurgeCSS } = require('purgecss');
+const purgeHtml = require('purgecss-from-html');
+const { cleanCSS } = require('./clean_css');
+const { HTML_TO_REMOVE } = require('./constants');
+const { die } = require('./utils');
+
+const cleanHtml = (html) => {
+ const $ = cheerio.load(html);
+
+ HTML_TO_REMOVE.forEach((selector) => {
+ $(selector).remove();
+ });
+
+ return $.html();
+};
+
+const mergePurgeCSSOptions = (...options) =>
+ mergeWith(...options, (objValue, srcValue) => {
+ if (isArray(objValue)) {
+ return objValue.concat(srcValue);
+ }
+
+ return undefined;
+ });
+
+const getStartupCSS = async ({ htmlPaths, cssPaths, purgeOptions }) => {
+ const content = htmlPaths.map((htmlPath) => {
+ if (!fs.existsSync(htmlPath)) {
+ die(`Could not find fixture "${htmlPath}". Have you run the fixtures?`);
+ }
+
+ const rawHtml = fs.readFileSync(htmlPath);
+ const html = cleanHtml(rawHtml);
+
+ return { raw: html, extension: 'html' };
+ });
+
+ const purgeCSSResult = await new PurgeCSS().purge({
+ content,
+ css: cssPaths,
+ ...mergePurgeCSSOptions(
+ {
+ fontFace: true,
+ variables: true,
+ keyframes: true,
+ blocklist: [/:hover/, /:focus/, /-webkit-/, /-moz-focusring-/, /-ms-expand/],
+ safelist: {
+ standard: ['brand-header-logo'],
+ },
+ // By default, PurgeCSS ignores special characters, but our utilities use "!"
+ defaultExtractor: (x) => x.match(/[\w-!]+/g),
+ extractors: [
+ {
+ extractor: purgeHtml,
+ extensions: ['html'],
+ },
+ ],
+ },
+ purgeOptions,
+ ),
+ });
+
+ return purgeCSSResult.map(({ css }) => cleanCSS(css)).join('\n');
+};
+
+module.exports = { getStartupCSS };
diff --git a/scripts/frontend/startup_css/main.js b/scripts/frontend/startup_css/main.js
new file mode 100644
index 00000000000..1e8dcbebae2
--- /dev/null
+++ b/scripts/frontend/startup_css/main.js
@@ -0,0 +1,60 @@
+const { memoize } = require('lodash');
+const { OUTPUTS } = require('./constants');
+const { getCSSPath } = require('./get_css_path');
+const { getStartupCSS } = require('./get_startup_css');
+const { log, die } = require('./utils');
+const { writeStartupSCSS } = require('./write_startup_scss');
+
+const memoizedCSSPath = memoize(getCSSPath);
+
+const runTask = async ({ outFile, htmlPaths, cssKeys, purgeOptions = {} }) => {
+ try {
+ log(`Generating startup CSS for HTML files: ${htmlPaths}`);
+ const generalCSS = await getStartupCSS({
+ htmlPaths,
+ cssPaths: cssKeys.map(memoizedCSSPath),
+ purgeOptions,
+ });
+
+ log(`Writing to startup CSS...`);
+ const startupCSSPath = writeStartupSCSS(outFile, generalCSS);
+ log(`Finished writing to ${startupCSSPath}`);
+
+ return {
+ success: true,
+ outFile,
+ };
+ } catch (e) {
+ log(`ERROR! Unexpected error occurred while generating startup CSS for: ${outFile}`);
+ log(e);
+
+ return {
+ success: false,
+ outFile,
+ };
+ }
+};
+
+const main = async () => {
+ const result = await Promise.all(OUTPUTS.map(runTask));
+ const fullSuccess = result.every((x) => x.success);
+
+ log('RESULTS:');
+ log('--------');
+
+ result.forEach(({ success, outFile }) => {
+ const status = success ? '✓' : 'ⅹ';
+
+ log(`${status}: ${outFile}`);
+ });
+
+ log('--------');
+
+ if (fullSuccess) {
+ log('Done!');
+ } else {
+ die('Some tasks have failed');
+ }
+};
+
+main();
diff --git a/scripts/frontend/startup_css/setup.sh b/scripts/frontend/startup_css/setup.sh
new file mode 100755
index 00000000000..795799bd9fd
--- /dev/null
+++ b/scripts/frontend/startup_css/setup.sh
@@ -0,0 +1,76 @@
+path_public_dir="public"
+path_tmp="tmp"
+path_dest="$path_tmp/startup_css_assets"
+glob_css_dest="$path_dest/application*.css"
+glob_css_src="$path_public_dir/assets/application*.css"
+should_clean=false
+
+should_force() {
+ $1=="force"
+}
+
+has_dest_already() {
+ find $glob_css_dest -quit
+}
+
+has_src_already() {
+ find $glob_css_src -quit
+}
+
+compile_assets() {
+ # We need to build the same test bundle that is built in CI
+ RAILS_ENV=test bundle exec rake rake:assets:precompile
+}
+
+clean_assets() {
+ bundle exec rake rake:assets:clobber
+}
+
+copy_assets() {
+ rm -rf $path_dest
+ mkdir $path_dest
+ cp $glob_css_src $path_dest
+}
+
+echo "-----------------------------------------------------------"
+echo "If you are run into any issues with Startup CSS generation,"
+echo "please check out the feedback issue:"
+echo ""
+echo "https://gitlab.com/gitlab-org/gitlab/-/issues/331812"
+echo "-----------------------------------------------------------"
+
+if [ ! -e $path_public_dir ]; then
+ echo "Could not find '$path_public_dir/'. This script must be run in the root directory of the gitlab project."
+ exit 1
+fi
+
+if [ ! -e $path_tmp ]; then
+ echo "Could not find '$path_tmp/'. This script must be run in the root directory of the gitlab project."
+ exit 1
+fi
+
+if [ "$1" != "force" ] && has_dest_already; then
+ echo "Already found assets for '$glob_css_dest'. Did you want to run this script with 'force' argument?"
+ exit 0
+fi
+
+# If we are in CI, don't recompile things...
+if [ -n "$CI" ]; then
+ if ! has_src_already; then
+ echo "Could not find '$glob_css_src'. Expected these artifacts to be generated by CI pipeline."
+ exit 1
+ fi
+elif has_src_already; then
+ echo "Found '$glob_css_src'. Skipping compile assets..."
+else
+ echo "Starting compile assets process..."
+ compile_assets
+ should_clean=true
+fi
+
+copy_assets
+
+if $should_clean; then
+ echo "Starting cleanup..."
+ clean_assets
+fi
diff --git a/scripts/frontend/startup_css/startup_css_changed.sh b/scripts/frontend/startup_css/startup_css_changed.sh
new file mode 100755
index 00000000000..f214e61cdfb
--- /dev/null
+++ b/scripts/frontend/startup_css/startup_css_changed.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+echo "-----------------------------------------------------------"
+echo "If you run into any issues with Startup CSS generation"
+echo "please check out the feedback issue:"
+echo ""
+echo "https://gitlab.com/gitlab-org/gitlab/-/issues/331812"
+echo "-----------------------------------------------------------"
+
+startup_glob="*stylesheets/startup*"
+
+echo "Staging changes to '${startup_glob}' so we can check for untracked files..."
+git add ${startup_glob}
+
+if [ -n "$(git diff HEAD --name-only -- ${startup_glob})" ]; then
+ diff=$(git diff HEAD -- ${startup_glob})
+ cat <<EOF
+
+Startup CSS changes detected!
+
+It looks like there have been recent changes which require
+regenerating the Startup CSS files.
+
+**What should I do now?**
+
+IMPORTANT: Please make sure to update your MR title with "[RUN AS-IF-FOSS]" and start a new MR pipeline
+
+To fix this job, consider one of the following options:
+
+ 1. Regenerating locally with "yarn run generate:startup_css".
+ 2. Copy and apply the following diff:
+
+----- start diff -----
+$diff
+
+----- end diff -------
+EOF
+
+ exit 1
+fi
diff --git a/scripts/frontend/startup_css/utils.js b/scripts/frontend/startup_css/utils.js
new file mode 100644
index 00000000000..49ad201fb6b
--- /dev/null
+++ b/scripts/frontend/startup_css/utils.js
@@ -0,0 +1,8 @@
+const die = (message) => {
+ console.log(message);
+ process.exit(1);
+};
+
+const log = (message) => console.error(`[gitlab.startup_css] ${message}`);
+
+module.exports = { die, log };
diff --git a/scripts/frontend/startup_css/write_startup_scss.js b/scripts/frontend/startup_css/write_startup_scss.js
new file mode 100644
index 00000000000..245681bada3
--- /dev/null
+++ b/scripts/frontend/startup_css/write_startup_scss.js
@@ -0,0 +1,28 @@
+const { writeFileSync } = require('fs');
+const path = require('path');
+const prettier = require('prettier');
+const { PATH_STARTUP_SCSS } = require('./constants');
+
+const buildFinalContent = (raw) => {
+ const content = `// DO NOT EDIT! This is auto-generated from "yarn run generate:startup_css"
+// Please see the feedback issue for more details and help:
+// https://gitlab.com/gitlab-org/gitlab/-/issues/331812
+@charset "UTF-8";
+${raw}
+@import 'startup/cloaking';
+@include cloak-startup-scss(none);
+`;
+
+ // We run prettier so that there is more determinism with the generated file.
+ return prettier.format(content, { parser: 'scss' });
+};
+
+const writeStartupSCSS = (name, raw) => {
+ const fullPath = path.join(PATH_STARTUP_SCSS, `${name}.scss`);
+
+ writeFileSync(fullPath, buildFinalContent(raw));
+
+ return fullPath;
+};
+
+module.exports = { writeStartupSCSS };