diff --git a/.github/workflows/build-jekyll.yml b/.github/workflows/build-jekyll.yml new file mode 100644 index 0000000000..1697ff809b --- /dev/null +++ b/.github/workflows/build-jekyll.yml @@ -0,0 +1,54 @@ +name: Build and Deploy +on: + pull_request: + branches: + - gh-pages + - 2025-redesign + push: + branches: + - gh-pages + - 2025-redesign # TODO: Remove before merge + workflow_dispatch: + +# Prevent concurrent deploys +concurrency: + group: "pages" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + bundler-cache: true + - name: Setup node + uses: bahmutov/npm-install@v1 + - name: Setup pages + id: pages + uses: actions/configure-pages@v5 + - name: Build Jekyll + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" --profile + env: + JEKYLL_ENV: production + RUBYOPT: --yjit + - name: Upload + uses: actions/upload-pages-artifact@v4 + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + permissions: + pages: write + id-token: write + # Only deploy on push, merge or manual + if: "github.event_name != 'pull_request'" + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/check-a11y-of-changed-content.yaml b/.github/workflows/check-a11y-of-changed-content.yaml index 3c3d887adc..5405978fe4 100644 --- a/.github/workflows/check-a11y-of-changed-content.yaml +++ b/.github/workflows/check-a11y-of-changed-content.yaml @@ -36,7 +36,7 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.3 + ruby-version: 3.4 - name: Install gems run: bundle config path vendor/bundle && bundle install diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 4d5bc3c8e3..dadbb3e069 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -2,8 +2,6 @@ name: Lint Posts on: pull_request: - branches: - - 'gh-pages' # Runs when a PR targets the gh-pages branch jobs: lint-posts: diff --git a/.pa11yci.js b/.pa11yci.js index 5a3aa9312c..34977a2580 100644 --- a/.pa11yci.js +++ b/.pa11yci.js @@ -1,33 +1,20 @@ /// This is a configuration file automatically picked up by pa11y-ci. -const relativeUrls = require('./pa11y-ci-urls'); +const relativeUrls = require("./pa11y-ci-urls"); -const chromiumBin = process.env.CHROMIUM_BIN; -if (!chromiumBin) { - throw new Error('CHROMIUM_BIN environment variable is not set'); -} - -const baseUrl = 'http://localhost:4000'; +const baseUrl = "http://localhost:4000"; // Colour contrast is a known issue. If we ever fix the brand colours, this should be removed. const colourContrastRuleIds = [ // HTML CodeSniffer rule IDs come from section 1.4.3 of: // https://squizlabs.github.io/HTML_CodeSniffer/Standards/WCAG2/ - 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail', // normal text - 'WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail', // large text + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail", // normal text + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail", // large text ]; module.exports = { defaults: { - chromeLaunchConfig: { - executablePath: chromiumBin, - "args": ["--no-sandbox"], - }, - ignore: [ - ...colourContrastRuleIds, - ], - reporter: 'cli', - runners: ['htmlcs'], + ignore: [...colourContrastRuleIds], }, urls: relativeUrls.map((url) => `${baseUrl}${url}`), }; diff --git a/.prettierrc.json5 b/.prettierrc.json5 new file mode 100644 index 0000000000..a0a917d304 --- /dev/null +++ b/.prettierrc.json5 @@ -0,0 +1,11 @@ +{ + plugins: ["@shopify/prettier-plugin-liquid"], + overrides: [ + { + "files": "*.html", + "options": { + parser: "liquid-html" + } + } + ] +} \ No newline at end of file diff --git a/Gemfile b/Gemfile index 037c726322..4d0c9d8f1c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,51 @@ source 'http://rubygems.org' -gem 'github-pages' +ruby '~> 3.3' +gem "jekyll", "~> 4" +group :jekyll_plugins do + # Deps from EOL github-pages gem minus themes + gem "jekyll-avatar" + gem "jekyll-commonmark" + gem "jekyll-default-layout" + gem "jekyll-feed" + gem "jekyll-gist" + gem "jekyll-github-metadata" + gem "jekyll-include-cache" + gem "jekyll-mentions" + gem "jekyll-optional-front-matter" + gem "jekyll-readme-index" + gem "jekyll-redirect-from" + gem "jekyll-relative-links" + gem "jekyll-remote-theme" + gem "jekyll-sass-converter" + gem "jekyll-seo-tag" + gem "jekyll-sitemap" + gem "jekyll-swiss" + gem "jekyll-titles-from-headings" + gem "jemoji" + gem "kramdown" + gem "kramdown-parser-gfm" + gem "liquid" + gem "mercenary" + gem "minima" + gem "nokogiri" + gem "rouge" + gem "terminal-table" + gem "webrick", "~> 1.7" -gem "tzinfo-data", "~> 1.2022" + gem 'jekyll-paginate-v2' -gem "webrick", "~> 1.7" + gem "tzinfo-data", "~> 1.2022" -# Issue with ffi requiring a very recent rubygems version, not yet available -# in many current linux docker images, so this must be locked down for now. -# See https://github.com/ffi/ffi/issues/1103 -gem "ffi", "< 1.17.0" + gem "generate-json", "1.0.0", path: "gems/generate-json-gem" + gem "custom-filters", "1.0.0", path: "gems/custom-filters-gem" + gem "html-minify", "1.0.0", path: "gems/html-minify-gem" + gem "hook-exec", "1.0.0", path: "gems/hook-exec-gem" + + gem "jekyll_plugin_support" + + # Issue with ffi requiring a very recent rubygems version, not yet available + # in many current linux docker images, so this must be locked down for now. + # See https://github.com/ffi/ffi/issues/1103 + gem "ffi", "< 1.17.0" +end diff --git a/README-DEV.md b/README-DEV.md new file mode 100644 index 0000000000..10b2822874 --- /dev/null +++ b/README-DEV.md @@ -0,0 +1,91 @@ +# Blog Development +Useful things for working the blog itself (not blog posts). + +## Setup +See [README.md](README.md). +It has everything needed to get the blog up and running. + +## HTML +Base layouts must be in `_layouts` and components must be in `_includes`. +Within components, prefer passing variables in using `{%- include foo.html bar=baz -%}` rather than using the page +context. +This allows for includes to be cached using `{%- include_cached ... -%}` if they begin to consume too much build time. + +A few custom filters are provided by the `custom-filters` plugin: +- `starts_with` - returns true if the given string starts with a given string + - `"https://example.com" | starts_with: "https"` resolves to `true` + - `"ftp://example.com" | starts_with: "https"` resolves to `false` + - `nil | starts_with: "https:"` resolves to `false` +- `ends_with` - returns true if the given string ends with a given string + - `"foo.html" | ends_with: "html"` resolves to `true` + - `"foo.scss" | ends_with: "html"` resolves to `false` + - `nil | ends_with: "html"` resolves to `false` + +## Styles +Blog stylesheets must be in `scss/`, be a partial stylesheet (be prefixed with an `_`) and `@use`'d by either +`assets/style.scss` or a stylesheet already `@use`'d by `assets/style.scss`. + +For example, the styles related to the author list are in `scss/_author-list.scss`, which is `@use`d by +`assets/style.scss`, but `scss/_colours.scss` isn't `@use`'d by `assets/style.scss`. +Rather `_colours` is `@use`d by other stylesheets such as `_author-list.scss`. + +Avoid deprecated SCSS features like `@import` where possible. +If it cannot be avoided, include a comment with the reason why. +For example `_util.scss` uses `@import` because the foundations framework still requires it. + +All stylesheets reachable from `assets/style.scss` are compiled and minified into `_site/assets/style.css` as part of +the build process, using Jekyll's builtin SCSS support. + +## Scripts +Blog scripts should be in `scripts/` and preferably be TypeScript (type safety is king). +Where possible, try and import new scripts into `scripts/index.ts`. +If this isn't possible, add the new script to `rspack.config.ts`'s `entry.page` array. + +The scripts are compiled, concatenated and minified using a custom build hook that triggers `rspack build`. + +## Build Hook +To aid in development, arbitrary commands can be run at different Jekyll hooks by adding entries to `_config.yml`'s +`hook_exec` entry. + +Each entry has the following structure: +```yaml +hook_exec: + owner: + event: + name: + cmd: string + env: optional map + priority: optional int +``` +where: +- `owner` and `event` are from the list in the [Jekyll docs](https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events) +with the `:` removed. +- `name` is a useful identifier for logging +- `cmd` is the command to run + - The value is treated as a [liquid template](https://shopify.github.io/liquid/) +- `env` is a map of environment variables + - Values in the map are treated as [liquid template](https://shopify.github.io/liquid/) +- `priority` is the priority of the hook with higher values being run first + +Liquid templates have access to the following variables, depending on which hook they are running in: +- `site` - hooks with `:site` owner + - Same as the `site` object in normal templates +- `doc` - hooks with `:document` owner + - [Jekyll document](https://github.com/jekyll/jekyll/blob/master/lib/jekyll/document.rb) +- `page` - hooks with `:pages` owner + - Same as the `page` object in normal template +- `payload` - hooks with either the `:pre_render` or `:post_render` event + - a hash containing variables to be used during rendering (`:pre_render`) or their final values after rendering +(`:post_render`) +- `files` - hooks with `:clean` owner + - list of [files](https://ruby-doc.org/3.4.1/File.html) to be deleted + +For example, the `:site` `:pre_render` hook has the `site` and `payload` variables. + +These templates do not have access to [Jekyll specific filters and tags](https://jekyllrb.com/docs/liquid/). + +If a command produces a file in `_site`, it must be added to `_config.yml`'s `keep_file` list to prevent Jekyll from +removing it as part of the build. + +This is primarily used to invoke external build commands. +Anything more complicated should be implemented as a custom plugin, similar to the `html-minify` plugin. \ No newline at end of file diff --git a/README.md b/README.md index d33bc152ed..39084f325d 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ GitHub account. The blog is a static website, designed to be hosted on [GitHub pages][github-pages]. -The underlying content is generated through a series of Ruby gems and libraries, starting with a dedicated github-pages -[gem][ruby-github-pages]. +The underlying content is generated through a series of Ruby gems and libraries. Within that stack, [Jekyll][jekyll-docs] is used as the static content generation engine, consuming template files written in either **HTML** or **Markdown** (syntax extended by [Kramdown][kramdown-syntax]). @@ -53,9 +52,18 @@ root named after your Scott Logic username. Within this you will need a set of f be obvious what needs changing. Then add yourself to `_data/authors.yml`, again using an existing author as a template. You will need to add -- an entry under `authors` +- an entry under `authors` with the following fields where _italic_ fields are required + - _name_ + - _picture_ + - author-summary + - twitter-url + - twitter-handle + - linkedin-url + - linkedin-handle - your username under `active-authors` +If both _social_-url and _social_-handle are provided, _social_-url is used. + Finally, if you performed a _sparse checkout_ as recommended, you will need to add directory `_posts` in the root of your local copy. @@ -81,7 +89,9 @@ rebuild. By far the easiest route is to use Docker: if you have it installed, you can [skip ahead][run-docker] now! The blog consists of static HTML pages with content generated using: + - [github-pages][ruby-github-pages] for deployment hooks + - [Jekyll][jekyll-docs] for static site generation generator - [Kramdown][kramdown-syntax] for an extended markdown syntax - [Liquid][ruby-liquid] for templating functionality @@ -107,7 +117,7 @@ sudo apt-get install ruby2.3 ruby2.3-dev build-essential dh-autoreconf libxslt-d ``` On Windows, if you use Chocolatey, simply run `choco install ruby` in a PowerShell instance -with elevated priveleges. If you don't use Chocolatey, you can use [RubyInstaller][rubyinstaller] +with elevated privileges. If you don't use Chocolatey, you can use [RubyInstaller][rubyinstaller] or see the Ruby website for [alternative ways to install Ruby][ruby-installation-instructions]. You don't need to install any other dependencies on Windows at this stage. @@ -127,16 +137,20 @@ gem update gem install jekyll bundler nokogiri ``` -Thirdly, configure Bundler to store project dependencies in `vendor/bundle`, and, -when in the root directory of your clone of the blog, install the project dependencies. +Optionally, configure Bundler to store project dependencies in `vendor/bundle` ```shell bundle config path vendor/bundle +``` + +When in the root directory of your clone of the blog, install the project dependencies. + +```shell cd PATH/TO/BLOG bundle install ``` -Finally, run `jekyll -v` to check whether Jekyll is working. If so, you're good to run the blog! +Finally, run `bundle exec jekyll -v` to check whether Jekyll is working. If so, you're good to run the blog! #### Running in the native environment @@ -147,16 +161,24 @@ Navigate to the root directory of your clone of the blog and execute Jekyll usin bundle exec jekyll serve ``` +See [jekyll's docs](https://jekyllrb.com/docs/configuration/options/) for command line flags. + The blog will then be available on [localhost][localhost]. -If you are working on fixes or new features, and need to re-compile the scripts or SCSS, you can use these npm scripts: +If you are working on fixes or new features, you can use these npm scripts: ```shell -npm ci -npm run scripts -npm run style +npm ci # Install deps +npm run prettier # Format non-post files ``` +##### Useful Command Line Flags for Jekyll + +- `--livereload` - trigger a build on file change (excluding SCSS or JS) and refresh the brower once built + +- `--incremental` - use the experimental incremental build mode which after the initial build, only builds changed files +- `RUBYOPT="--yjit"` - let ruby use its JIT (only macOS, Linux and BSD on x86-64 and arm64/aarch64 CPUs are supported) + ### Running with Docker Use a bash-compatible shell; Git bash on Windows should work fine. @@ -164,7 +186,7 @@ Use a bash-compatible shell; Git bash on Windows should work fine. #### Install gem dependencies First, we output gem dependencies to directory `container_gem_cache` on the host machine. This is analogous to running -"npm install" for an npm package: +"npm install" for a npm package: ```shell ./shell/docker-gem-install.sh @@ -221,15 +243,12 @@ changes. This workflow runs only on a manual dispatch on the `gh-pages` branch. [calibreapp-image-actions]: https://github.com/calibreapp/image-actions [confluence-getting-started]: https://scottlogic.atlassian.net/wiki/spaces/INT/pages/3577479175/Getting+started+with+the+Scott+Logic+blog -[sparse-checkout-guide]: https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/#sparse-checkout-and-partial-clones [github-pages]: https://pages.github.com/ -[github-pages-docs]: https://docs.github.com/en/pages [run-docker]: #running-with-docker [jekyll-docs]: https://jekyllrb.com/docs/ [kramdown-syntax]: https://kramdown.gettalong.org/syntax.html [localhost]: http://localhost:4000 -[ruby-github-pages]: https://rubygems.org/gems/github-pages [ruby-bundler]: https://bundler.io/ [rubyinstaller]: https://rubyinstaller.org/ [ruby-installation-instructions]: https://www.ruby-lang.org/en/documentation/installation diff --git a/_config.yml b/_config.yml index df288d22fb..a4571a800f 100644 --- a/_config.yml +++ b/_config.yml @@ -12,16 +12,17 @@ collections: title: Uploads output: true defaults: -- scope: - path: '' - type: posts - values: - summary: a quick summary of your post - author: the name of the authers account - layout: default_post + - scope: + path: "" + type: posts + values: + summary: a quick summary of your post + author: the name of the authors account + layout: default_post repository: ScottLogic/blog name: Scott Logic -description: This feed is brought to you by Scott Logic's team of technical authors +description: + This feed is brought to you by Scott Logic's team of technical authors and bloggers, covering topics including HTML5, iOS, C#, process, UX and practically anything else relating to software development. canonical: @@ -31,26 +32,64 @@ scottlogic: applause-button-api: url: https://ip2o6c571d.execute-api.eu-west-2.amazonaws.com/production permalink: "/:year/:month/:day/:title.html" -safe: true +safe: false twitter_handle: Scott_Logic plugins: -- jekyll-redirect-from + - jekyll-redirect-from + - jekyll-paginate-v2 + - generate-json + - custom-filters + - html-minify + - hook-exec + - jekyll-include-cache kramdown: input: kramdown -paginate: 20 -excerpt_separator: +pagination: + enabled: true + per_page: 12 + permalink: "/page/:num/" + sort_reverse: true + trail: + before: 2 + after: 2 +baseurl: "" +excerpt_separator: exclude: -- CNAME -- Gemfile -- Gemfile.lock -- lintPosts.js -- scss -- node_modules -- package.json -- README.md -- ".spelling" -- scripts/jquery-1.9.1.js -- scripts/jquery.jscroll-2.2.4.js -- scripts/tweet.js -- scripts/search.js -- vendor + - ".idea" + - ".prettierrc.json5" + - ".spelling" + - CNAME + - Gemfile + - Gemfile.lock + - PULL_REQUEST_TEMPLATE + - README.md + - README-DEV.md + - gems + - generate_pa11y_ci_urls_from_git_diff.sh + - lintPosts.js + - node_modules + - package-lock.json + - package.json + - rspack.config.ts + - scripts + - scss + - send-blogs-to-deploy-endpoint.js + - tsconfig.json + - vendor +keep_files: + - "script.js" +sass: + style: compressed + sourcemap: development + sass_dir: scss + quiet_deps: true + load_paths: + - "node_modules/applause-button/dist/" + - "node_modules/cookieconsent/build/" +hook_exec: + site: + post_write: + bundle-js: + env: + BASE_URL: "{{ site.baseurl }}" + cmd: "./node_modules/.bin/rspack build" \ No newline at end of file diff --git a/_data/categories.yml b/_data/categories.yml index b1e6dffeed..ff9282bc4f 100644 --- a/_data/categories.yml +++ b/_data/categories.yml @@ -1,42 +1,41 @@ -- title: Latest Articles - url: /index.html +- title: Latest Articles + url: /index.html -- title: Resources - url: /category/resources.html +- title: Resources + url: /category/resources/ -- title: Cloud - url: /category/cloud.html +- title: Cloud + url: /category/cloud/ -- title: Tech - url: /category/tech.html +- title: Tech + url: /category/tech/ -- title: UX Design - url: /category/ux.html +- title: UX Design + url: /category/ux/ -- title: Delivery - url: /category/delivery.html +- title: Delivery + url: /category/delivery/ -- title: Testing - url: /category/test.html +- title: Testing + url: /category/testing/ -- title: Artificial Intelligence - url: /category/ai.html +- title: Artificial Intelligence + url: /category/ai/ -- title: Sustainability - url: /category/sustainability.html +- title: Sustainability + url: /category/sustainability/ -- title: Data Engineering - url: /category/data-engineering.html +- title: Data Engineering + url: /category/data-engineering/ -- title: People - url: /category/people.html +- title: People + url: /category/people/ -- title: Videos - url: /category/videos.html +- title: Videos + url: /category/videos/ -- title: Open Source - url: /category/open-source.html - -- title: Podcast - url: /category/podcast.html +- title: Open Source + url: /category/open-source/ +- title: Podcast + url: /category/podcast/ diff --git a/_data/related.yml b/_data/related.yml index 3ebdafa520..9f1fd2a4ac 100644 --- a/_data/related.yml +++ b/_data/related.yml @@ -3372,4 +3372,4 @@ - /2014/02/28/arrow-functions-in-knockoutjs.html /2025/10/03/delegating-grunt-work.html: - /2025/08/21/is-ai-taking-over-testing-here-is-why-human-insight-still-matters.html - - /2025/09/10/leveraging-copilot-for-refactoring.html \ No newline at end of file + - /2025/09/10/leveraging-copilot-for-refactoring.html diff --git a/_includes/author-link.html b/_includes/author-link.html index 8ada390dc6..d564c922f5 100644 --- a/_includes/author-link.html +++ b/_includes/author-link.html @@ -1,3 +1,3 @@ -{% assign author-id = include.author-id %} -{% assign author = site.data.authors.authors[author-id] %} -{{ author.name }} \ No newline at end of file +{%- assign author-id = include['author-id'] %} +{%- assign author = site.data.authors.authors[author-id] %} +{{- author.name -}} diff --git a/_includes/author.html b/_includes/author.html index 658a35620f..059324dab9 100644 --- a/_includes/author.html +++ b/_includes/author.html @@ -1,29 +1,25 @@ -{% assign author = include.author %} +{%- assign author = include.author -%}
diff --git a/_includes/author_list.html b/_includes/author_list.html index efcf983d04..5314ca0abb 100644 --- a/_includes/author_list.html +++ b/_includes/author_list.html @@ -1,37 +1,7 @@ -{% assign currentAuthorName = include.currentAuthorName %} -