{"id":29547241,"url":"https://github.com/chrisnajman/github-pages-optimised-build","last_synced_at":"2026-07-06T16:31:18.457Z","repository":{"id":303192977,"uuid":"1014679993","full_name":"chrisnajman/github-pages-optimised-build","owner":"chrisnajman","description":"GitHub Pages site template built from the '/docs' folder using an automated front-end build process with minified HTML, CSS and (transpiled) JavaScript.","archived":false,"fork":false,"pushed_at":"2025-07-06T08:50:37.000Z","size":308,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-06T09:21:21.089Z","etag":null,"topics":["css-nesting","cssnano","es6-modules","esbuild","html-css-javascript","html-minifier-terser","postcss","postcss-cli","postcss-import","postcss-nesting","shx"],"latest_commit_sha":null,"homepage":"https://chrisnajman.github.io/github-pages-optimised-build/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chrisnajman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-06T07:38:53.000Z","updated_at":"2025-07-06T08:50:40.000Z","dependencies_parsed_at":"2025-07-06T09:21:34.853Z","dependency_job_id":null,"html_url":"https://github.com/chrisnajman/github-pages-optimised-build","commit_stats":null,"previous_names":["chrisnajman/github-pages-optimised-build"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chrisnajman/github-pages-optimised-build","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisnajman%2Fgithub-pages-optimised-build","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisnajman%2Fgithub-pages-optimised-build/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisnajman%2Fgithub-pages-optimised-build/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisnajman%2Fgithub-pages-optimised-build/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chrisnajman","download_url":"https://codeload.github.com/chrisnajman/github-pages-optimised-build/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chrisnajman%2Fgithub-pages-optimised-build/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265654551,"owners_count":23805932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css-nesting","cssnano","es6-modules","esbuild","html-css-javascript","html-minifier-terser","postcss","postcss-cli","postcss-import","postcss-nesting","shx"],"created_at":"2025-07-17T20:00:54.256Z","updated_at":"2026-07-06T16:31:18.441Z","avatar_url":"https://github.com/chrisnajman.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GitHub Pages Optimised Build\n\n\u003e [!IMPORTANT]  \n\u003e The following assumes you have Node.js installed on your machine.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong id=\"menu\"\u003eMenu\u003c/strong\u003e\u003c/summary\u003e\n \n  - [Description](#description)\n  - [Build \u0026 Deployment Setup for /docs Folder](#build--deployment-setup-for-docs-folder)\n  - [CSS](#css)\n  - [JavaScript](#javascript)\n  - [No JS](#no-js)\n  - [Accessibility](#accessibility)\n  - [Theme Toggling](#theme-toggling)\n  - [Testing and Compatibility](#testing-and-compatibility)\n  - [How to Run](#how-to-run)\n\n\u003c/details\u003e\n\n## Description\n\n**GitHub Pages Optimised Build** is a test project designed to demonstrate an automated front-end build pipeline using modern tooling. The key purpose is to:\n\n- Automatically bundle and minify JavaScript, process and minify CSS (including nesting), and minify HTML\n- Save all production-ready assets into a `/docs` folder\n- Configure GitHub Pages to serve the site directly from `/docs`\n\nThis approach ensures the original development files (with modular JS, nested CSS, and unminified HTML) stay intact in the root of the project, while the optimized output is safely contained and served from `/docs`.\n\nThe build process is fully scriptable using NPM scripts, with no reliance on tools like Webpack or Gulp.\n\n[View on GitPage](https://chrisnajman.github.io/github-pages-optimised-build)\n\n[Back to menu](#menu)\n\n---\n\n### Why `/docs`?\n\n- The `/docs` folder contains optimized production-ready assets, including:\n  - `bundle.js`: the JavaScript bundled into a single file (no modules)\n  - `style.min.css`: the CSS processed with nesting flattened and minified\n- The original source files (`index.js`, files in `js-modules/`, `style.css` with nesting, etc.) remain in the project root for easier development and editing.\n- GitHub Pages is configured to serve the site from the `/docs` folder instead of the root, so your published site uses these optimized files.\n\n\u003e [!IMPORTANT]\n\u003e The build process only supports a **flat** HTML structure, i.e. all HTML pages must sit in the root of the project (no folders).\n\n\u003e [!IMPORTANT]\n\u003e When publishing to GitHub Pages, make sure the Pages setting is configured to serve from the `/docs` folder on the `main` branch.\n\n[Back to menu](#menu)\n\n---\n\n## Build \u0026 Deployment Setup for `/docs` Folder\n\n### 1. Install Required Packages\n\nRun this once in your project root to install dev dependencies:\n\n```bash\nnpm install --save-dev esbuild html-minifier-terser postcss postcss-cli postcss-import postcss-nesting cssnano shx\n```\n\n- **esbuild** — bundles, transpiles (to ES2015), and minifies JS\n- **html-minifier-terser** — minifies HTML files in the `/docs` folder after copying and post-processing\n- **postcss, postcss-cli** — for CSS processing\n- **postcss-import** — enables @import rules in CSS for modular stylesheets\n- **postcss-nesting** — enables CSS nesting support\n- **postcss-url** — processes and rewrites URLs in CSS (e.g., copying assets and adjusting paths) for correct referencing in the build output\n- **cssnano** — minifies CSS\n- **shx** — cross-platform CLI utility (copy, rm, etc.)\n\n\u003e [!NOTE]\n\u003e For the current project, `package.json` and `package-lock.json` already contain references to all the required `npm` packages, so all you have to do is run `npm install` in the terminal to install `node_modules` (which will contain the required packages).\n\n---\n\n### 2. Create `postcss.config.js` in project root\n\n```js\nmodule.exports = {\n  plugins: [\n    require(\"postcss-import\"),\n    require(\"postcss-nesting\"),\n    require(\"postcss-url\")({\n      url: \"copy\",\n      assetsPath: \"img\",\n      useHash: true,\n    }),\n    require(\"cssnano\")({ preset: \"default\" }),\n  ],\n}}\n```\n\n\u003e [!NOTE]\n\u003e For the current project, `postcss.config.js` already exists in the project root.\n\n---\n\n### 3. Add build scripts to `package.json`\n\n```json\n{\n  \"scripts\": {\n    \"build:js\": \"esbuild index.js --bundle --minify --target=es2015 --outfile=docs/bundle.js\", // bundles, transpiles to ES2015, and minifies\n    \"build:css\": \"postcss style.css --output docs/style.min.css\",\n    \"copy:assets\": \"shx cp -r img favicon.ico favicon-16x16.png favicon-32x32.png apple-touch-icon.png android-chrome-192x192.png android-chrome-512x512.png docs/\", // Add or remove items from here as required - do not touch shx cp -r OR docs/\n    \"copy:html\": \"shx cp *.html docs/\",\n    \"postprocess:html\": \"node scripts/postprocess-html.js\",\n    \"build\": \"npm run build:js \u0026\u0026 npm run build:css \u0026\u0026 npm run copy:assets \u0026\u0026 npm run copy:html \u0026\u0026 npm run postprocess:html\"\n  }\n}\n```\n\n\u003e [!NOTE]\n\u003e For the current project, build scripts have already been added to `package.json`.\n\n\u003e [!NOTE]\n\u003e The `copy:assets` script includes the required files for the current project. You can edit it to include additional folders or assets as needed.\n\n\u003e [!WARNING]\n\u003e Do **not** include `node_modules`, `scripts/postprocess-html.js`, `.gitignore`, `README.md` or `LICENSE` in the `copy:assets` script.\n\n---\n\n### 4. Create the HTML post-processing script\n\nCreate a folder called `scripts` in your project root, and inside it, create a file named `postprocess-html.js` with this content:\n\n```js\nconst fs = require(\"fs\")\nconst path = require(\"path\")\nconst { minify } = require(\"html-minifier-terser\")\n\nconst docsDir = \"docs\"\n\nconst minifyOptions = {\n  collapseWhitespace: true,\n  removeComments: true,\n  removeRedundantAttributes: true,\n  removeEmptyAttributes: true,\n  minifyCSS: true,\n  minifyJS: true,\n}\n\n;(async () =\u003e {\n  const files = fs.readdirSync(docsDir)\n\n  for (const file of files) {\n    if (file.endsWith(\".html\")) {\n      const filePath = path.join(docsDir, file)\n      const rawContent = fs.readFileSync(filePath, \"utf-8\")\n\n      try {\n        let content = await minify(rawContent, minifyOptions)\n\n        // Replace JS and CSS references\n        content = content\n          .replace(\n            /\u003cscript[^\u003e]*\\s(src=[\"']\\.?\\/index\\.js[\"'])[^\u003e]*type=[\"']module[\"'][^\u003e]*\u003e\u003c\\/script\u003e/i,\n            '\u003cscript src=\"./bundle.js\" defer\u003e\u003c/script\u003e'\n          )\n\n          .replace(\n            /\u003clink[^\u003e]+href=[\"']\\.?\\/style\\.css[\"'][^\u003e]*\u003e/i,\n            '\u003clink rel=\"stylesheet\" href=\"./style.min.css\"\u003e'\n          )\n\n        fs.writeFileSync(filePath, content, \"utf-8\")\n      } catch (err) {\n        console.error(`Failed to process ${file}:`, err)\n      }\n    }\n  }\n})()\n```\n\n\u003e [!NOTE]\n\u003e For the current project, `scripts/postprocess-html.js` already exists in the project root.\n\n---\n\n### 5. Run the full build process\n\nIn the terminal, run:\n\n```bash\nnpm run build\n```\n\nThis will:\n\n- Bundle and minify JS into `/docs/bundle.js`\n- Process and minify CSS into `/docs/style.min.css`\n- Copy assets (folders like `/css`, `/img`, `/fonts`, and files like `favicon.ico`) into `/docs`\n- Copy all root HTML files into `/docs`\n- Modify the copied HTML files in `/docs` to reference the optimized JS and CSS files\n\n\u003e [!NOTE]\n\u003e In the current project, the initial `npm run build` has already been run. However, any subsequent edits made to files and assets in the root of the project will necessitate further builds.\n\n[Back to menu](#menu)\n\n---\n\n## CSS\n\nBuilt with modern CSS features such as nesting, custom properties, and the `:has()` pseudo-class, this project emphasizes modular, accessible, and maintainable styling.\n\nThe main `style.css` file serves as an entry point and imports individual CSS modules using `@import`. These are then processed by PostCSS (with `postcss-import`, `postcss-nesting`, and `cssnano`) during the build.\n\nThe CSS has been split into separate modules, improving organization and reusability:\n\n- `root.css`: Global CSS custom properties (`--variables`) for themes, layout, and design tokens.\n- `base.css`: Global reset and base element styles.\n- `navigation.css`: Styles the primary navigation and hamburger menu.\n  - **Update 17/10/25**: mobile menu now positioned absolutely.\n- `theme-toggler.css`: Styles the dark/light mode toggle control.\n- `loader.css`: Styles the full-screen loading overlay that appears while the page is initializing.\n- `sticky-header.css`: (Optional. Delete if not required.) On scrolling the header always remains on top and the content slides under it.\n- `no-js.css`: See [No JS](#no-js), below.\n- `project-specific.css`: Contains small, per-project overrides or additions.\n\n\u003e [!NOTE]\n\u003e If you later revert to a single stylesheet without imports, the build process will continue to work seamlessly, as `postcss-import` gracefully handles the absence of `@import` statements.\n\n[Back to menu](#menu)\n\n---\n\n## JavaScript\n\nBuilt with **vanilla ES6 JavaScript**, focusing on modern syntax and browser APIs, , then bundled, transpiled to ES2015, and minified for broad browser compatibility.\n\nThe JavaScript has been split into separate modules, improving code modularity:\n\n- `module-placeholder.js`: Empty module, imported into `index.js`.\n- `primary-navigation.js` and `hamburger-button.js`: See [Accessible Mobile Menu Git repository](https://github.com/chrisnajman/accessible-mobile-menu)\n- `loader.js`: Displays a loader animation until the page is fully rendered, then removes the loader and announces readiness for screen readers.\n- `theme.js`: Handles theme toggling (light/dark mode) and local storage management.\n- `page-header-resize-observer.js`: (Optional. Delete if not required.) Observes the `.header `element and updates the root's `scroll-padding-top` based on header size and the `--skip-link-gap` CSS variable (in `base.css`), ensuring skip links and anchor targets scroll into view with proper spacing below a fixed or sticky header.\n\n[Back to menu](#menu)\n\n---\n\n## No JS\n\nIf JavaScript is disabled, a warning message is displayed to the user.\n\nThe `no-js` class provides a simple fallback mechanism that lets you adjust styles and messages when JavaScript is unavailable.\n\n### How it Works\n\n#### HTML\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\n  lang=\"en\"\n  class=\"no-js\"\n\u003e\n  \u003chead\u003e\n    \u003c!-- If JavaScript IS enabled, remove the 'no-js' class from \u003chtml\u003e tag --\u003e\n    \u003cscript\u003e\n      document.documentElement.classList.remove(\"no-js\")\n    \u003c/script\u003e\n\n    \u003c!-- Rest of \u003chead\u003e items --\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003c!-- \u003cbody\u003e content --\u003e\n    \u003cnoscript\u003e\n      \u003cp\u003e[Message to user, e.g. JavaScript is disabled, etc.]\u003c/p\u003e\n    \u003c/noscript\u003e\n    \u003c!-- \u003cbody\u003e content --\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe message you display depends on how much of your site relies on JavaScript:\n\n- Non-essential functionality: A short notice such as \"Certain features require JavaScript. Enable it to access all functionality.\" is usually sufficient.\n\n- JavaScript-dependent apps: If the site requires JavaScript to function, use a more specific message, for example: \"This application requires JavaScript to [perform its main function].\"\n\n#### CSS\n\n`no-js.css`:\n\n```css\n.no-js {\n  \u0026 .theme-toggler,\n  \u0026 .loader,\n  \u0026 .loader::after,\n  \u0026 .hamburger-button-wrapper,\n  \u0026 hr,\n  \u0026 .sticky-header-text {\n    display: none;\n  }\n\n  \u0026 .header {\n    position: static;\n  }\n\n  \u0026 .primary-navigation nav.menu-hidden {\n    display: flex;\n    width: fit-content;\n    transition: none;\n    pointer-events: auto;\n\n    \u0026 ul {\n      flex-wrap: wrap;\n      flex-direction: row;\n      justify-content: center;\n      gap: 1.25rem;\n    }\n  }\n}\n\nnoscript p {\n  width: fit-content;\n  margin-inline: auto;\n  border: 0.1875rem solid var(--warning);\n  background-color: var(--body-fg);\n  color: var(--el-bg);\n  padding: 1em 1.5em;\n  border-radius: 0.75rem;\n  text-align: center;\n  text-wrap: balance;\n}\n```\n\nThis CSS is specific to this project. In general, add any elements you want hidden inside the `.no-js { ... }` block, and style the `noscript p` element however you prefer.\n\n[Back to menu](#menu)\n\n---\n\n## Accessibility\n\nThe site is fully navigable using tab keys and up/down arrows.\n\n[Back to menu](#menu)\n\n---\n\n## Theme Toggling\n\nThe application includes a dark mode and light mode toggle:\n\n- The current theme state is stored in **local storage** and applied automatically on page reload.\n- Accessible buttons with appropriate ARIA attributes are used to improve usability.\n\n\u003e [!IMPORTANT]\n\u003e Remember to change `const LOCAL_STORAGE_PREFIX` in `js-modules/theme.js` to a unique identifier suitable for your project.\n\n[Back to menu](#menu)\n\n---\n\n## Testing and Compatibility\n\nThe application has been tested on the following platforms and browsers:\n\n- **Operating System**: Windows 10/11\n- **Browsers**:\n  - Google Chrome\n  - Mozilla Firefox\n  - Microsoft Edge\n\n### Device View Testing\n\nThe layout and functionality have been verified in both browser and device simulation views to ensure responsiveness and usability.\n\n[Back to menu](#menu)\n\n---\n\n## How to Run\n\n1. Clone or download the repository to your local machine.\n2. Open the project folder and start a simple HTTP server (e.g., using `Live Server` in VS Code or Python's `http.server` module).\n3. Open the project in a modern browser (e.g., Chrome, Firefox, or Edge).\n\n[Back to menu](#menu)\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisnajman%2Fgithub-pages-optimised-build","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrisnajman%2Fgithub-pages-optimised-build","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisnajman%2Fgithub-pages-optimised-build/lists"}