How We Built a New Home for WordPress.com Developers Using the Twenty Twenty-Four Theme

In the last few weeks, our team here at WordPress.com has rebuilt developer.wordpress.com from the ground up. If you build or design websites for other people, in any capacity, bookmark this site. It’s your new home for docs, resources, the latest news about developer features, and more. 

Rather than creating a unique, custom theme, we went all-in on using Twenty Twenty-Four, which is the default theme for all WordPress sites. 

That’s right, with a combination of built-in Site Editor functionalities and traditional PHP templates, we were able to create a site from scratch to house all of our developer resources. 

Below, I outline exactly how our team did it.

A Twenty Twenty-Four Child Theme

The developer.wordpress.com site has existed for years, but we realized that it needed an overhaul in order to modernize the look and feel of the site with our current branding, as well as accommodate our new developer documentation. 

You’ll probably agree that the site needed a refresh; here’s what developer.wordpress.com looked like two weeks ago:

Once we decided to redesign and rebuild the site, we had two options: 1) build it entirely from scratch or 2) use an existing theme. 

We knew we wanted to use Full Site Editing (FSE) because it would allow us to easily use existing patterns and give our content team the best writing and editing experience without them having to commit code.

We considered starting from scratch and using the official “Create Block Theme” plugin. Building a new theme from scratch is a great option if you need something tailored to your specific needs, but Twenty Twenty-Four was already close to what we wanted, and it would give us a headstart because we can inherit most styles, templates, and code from the parent theme.

We quickly decided on a hybrid theme approach: we would use FSE as much as possible but still fall back to CSS and classic PHP templates where needed (like for our Docs custom post type).

With this in mind, we created a minimal child theme based on Twenty Twenty-Four.

Spin up a scaffold with @wordpress/create-block

We initialized our new theme by running npx @wordpress/create-block@latest wpcom-developer. 

This gave us a folder with example code, build scripts, and a plugin that would load a custom block.

If you only need a custom block (not a theme), you’re all set.

But we’re building a theme here! Let’s work on that next.

Modify the setup into a child theme

First, we deleted wpcom-developer.php, the file responsible for loading our block via a plugin. We also added a functions.php file and a style.css file with the expected syntax required to identify this as a child theme. 

Despite being a CSS file, we’re not adding any styles to the style.css file. Instead, you can think of it like a documentation file where Template: twentytwentyfour specifies that the new theme we’re creating is a child theme of Twenty Twenty-Four.

/*
Theme Name: wpcom-developer
Theme URI: https://developer.wordpress.com
Description: Twenty Twenty-Four Child theme for Developer.WordPress.com
Author: Automattic
Author URI: https://automattic.com
Template: twentytwentyfour
Version: 1.0.0
*/

We removed all of the demo files in the “src” folder and added two folders inside: one for CSS and one for JS, each containing an empty file that will be the entry point for building our code.

The theme folder structure now looked like this:

The build scripts in @wordpress/create-block can build SCSS/CSS and TS/JS out of the box. It uses Webpack behind the scenes and provides a standard configuration. We can extend the default configuration further with custom entry points and plugins by adding our own webpack.config.js file. 

By doing this, we can:

Build specific output files for certain sections of the site. In our case, we have both PHP templates and FSE templates from both custom code and our parent Twenty Twenty-Four theme. The FSE templates need minimal (if any) custom styling (thanks to theme.json), but our developer documentation area of the site uses a custom post type and page templates that require CSS.

Remove empty JS files after building the *.asset.php files. Without this, an empty JS file will be generated for each CSS file.

Since the build process in WordPress Scripts relies on Webpack, we have complete control over how we want to modify or extend the build process. 

Next, we installed the required packages:

​​npm install path webpack-remove-empty-scripts –save-dev

Our webpack.config.js ended up looking similar to the code below. Notice that we’re simply extending the defaultConfig with a few extra properties.

Any additional entry points, in our case src/docs, can be added as a separate entry in the entry object.

// WordPress webpack config.
const defaultConfig = require( ‘@wordpress/scripts/config/webpack.config’ );

// Plugins.
const RemoveEmptyScriptsPlugin = require( ‘webpack-remove-empty-scripts’ );

// Utilities.
const path = require( ‘path’ );

// Add any new entry points by extending the webpack config.
module.exports = {
…defaultConfig,
…{
entry: {
‘css/global': path.resolve( process.cwd(), ‘src/css’, ‘global.scss’ ),
‘js/index': path.resolve( process.cwd(), ‘src/js’, ‘index.js’ ),
},
plugins: [
// Include WP’s plugin config.
…defaultConfig.plugins,
// Removes the empty `.js` files generated by webpack but
// sets it after WP has generated its `*.asset.php` file.
new RemoveEmptyScriptsPlugin( {
stage: RemoveEmptyScriptsPlugin.STAGE_AFTER_PROCESS_PLUGINS
} )
]
}
};

In functions.php, we enqueue our built assets and files depending on specific conditions. For example, we built separate CSS files for the docs area of the site, and we only enqueued those CSS files for our docs. 

<?php

function wpcom_developer_enqueue_styles() : void {
wp_enqueue_style( ‘wpcom-developer-style’,
get_stylesheet_directory_uri() . ‘/build/css/global.css’
);
}

add_action( ‘wp_enqueue_scripts’, ‘wpcom_developer_enqueue_styles’ );

We didn’t need to register the style files from Twenty Twenty-Four, as WordPress handles these inline.

We did need to enqueue the styles for our classic, non-FSE templates (in the case of our developer docs) or any additional styles we wanted to add on top of the FSE styles.

To build the production JS and CSS locally, we run npm run build. 

For local development, you can run npm run start in one terminal window and npx wp-env start (using the wp-env package) in another to start a local WordPress development server running your theme.

While building this site, our team of designers, developers, and content writers used a WordPress.com staging site so that changes did not affect the existing developer.wordpress.com site until we were ready to launch this new theme.

theme.json

Twenty Twenty-Four has a comprehensive theme.json file that defines its styles. By default, our hybrid theme inherits all of the style definitions from the parent (Twenty Twenty-Four) theme.json file. 

We selectively overwrote the parts we wanted to change (the color palette, fonts, and other brand elements), leaving the rest to be loaded from the parent theme. 

WordPress handles this merging, as well as any changes you make in the editor. 

Many of the default styles worked well for us, and we ended up with a compact theme.json file that defines colors, fonts, and gradients. Having a copy of the parent theme’s theme.json file makes it easier to see how colors are referenced.

You can change theme.json in your favorite code editor, or you can change it directly in the WordPress editor and then download the theme files from Gutenberg.

Why might you want to export your editor changes? Styles can then be transferred back to code to ensure they match and make it easier to distribute your theme or move it from a local development site to a live site. This ensures the FSE page templates are kept in code with version control. 

When we launched this new theme on production, the template files loaded from our theme directory; we didn’t need to import database records containing the template syntax or global styles.

Global styles in SCSS/CSS

Global styles are added as CSS variables, and they can be referenced in CSS. Changing the value in theme.json will also ensure that the other colors are updated.

For example, here’s how we reference our “contrast” color as a border color:

border-color: var(–wp–preset–color–contrast);

What about header.php and footer.php?

Some plugins require these files in a theme, e.g. by calling get_header(), which does not automatically load the FSE header template. 

We did not want to recreate our header and footer to cover those cases; having just one source of truth is a lot better.

By using do_blocks(), we were able to render our needed header block. Here’s an example from a header template file:

<head>
<?php
wp_head();
$fse_header_block = do_blocks( ‘<!– wp:template-part {"slug":"header","theme":"a8c/wpcom-developer","tagName":"header","area":"header", "className":"header-legacy"} /–>’ );
?>
</head>
<body <?php body_class(); ?>>
<?php
echo $fse_header_block;

The new developer.wordpress.com site is now live!

Check out our new-and-improved developer.wordpress.com site today, and leave a comment below telling us what you think. We’d love your feedback. 

Using custom code and staging sites are just two of the many developer features available to WordPress.com sites that we used to build our new and improved developer.wordpress.com.

If you’re a developer and interested in getting early access to other development-related features, click here to enable our “I am a developer” setting on your WordPress.com account.

Quelle: RedHat Stack

Azure Container Registry and Docker Hub: Connecting the Dots with Seamless Authentication and Artifact Cache

By leveraging the wide array of public images available on Docker Hub, developers can accelerate development workflows, enhance productivity, and, ultimately, ship scalable applications that run like clockwork. When building with public content, acknowledging the potential operational risks associated with using that content without proper authentication is crucial. 

In this post, we will describe best practices for mitigating these risks and ensuring the security and reliability of your containers.

Import public content locally

There are several advantages to importing public content locally. Doing so improves the availability and reliability of your public content pipeline and protects you from failed CI builds. By importing your public content, you can easily validate, verify, and deploy images to help run your business more reliably.

For more information on this best practice, check out the Open Container Initiative’s guide on Consuming Public Content.

Configure Artifact Cache to consume public content

Another best practice is to configure Artifact Cache to consume public content. Azure Container Registry’s (ACR) Artifact Cache feature allows you to cache your container artifacts in your own Azure Container Registry, even for private networks. This approach limits the impact of rate limits and dramatically increases pull reliability when combined with geo-replicated ACR, allowing you to pull artifacts from the region closest to your Azure resource. 

Additionally, ACR offers various security features, such as private networks, firewall configuration, service principals, and more, which can help you secure your container workloads. For complete information on using public content with ACR Artifact Cache, refer to the Artifact Cache technical documentation.

Authenticate pulls with public registries

We recommend authenticating your pull requests to Docker Hub using subscription credentials. Docker Hub offers developers the ability to authenticate when building with public library content. Authenticated users also have access to pull content directly from private repositories. For more information, visit the Docker subscriptions page. Microsoft Artifact Cache also supports authenticating with other public registries, providing an additional layer of security for your container workloads.

Following these best practices when using public content from Docker Hub can help mitigate security and reliability risks in your development and operational cycles. By importing public content locally, configuring Artifact Cache, and setting up preferred authentication methods, you can ensure your container workloads are secure and reliable.

Learn more about securing containers

Try Docker Scout to assess your images for security risks.

Looking to get up and running? Use our Quickstart guide.

Have questions? The Docker community is here to help.

Subscribe to the Docker Newsletter to stay updated with Docker news and announcements.

Additional resources for improving container security for Microsoft and Docker customers

Visit Microsoft Learn.

Read the introduction to Microsoft’s framework for securing containers.

Learn how to manage public content with Azure Container Registry.

Quelle: https://blog.docker.com/feed/

Revolutionize Your CI/CD Pipeline: Integrating Testcontainers and Bazel

One of the challenges in modern software development is being able to release software often and with confidence. This can only be achieved when you have a good CI/CD setup in place that can test your software and release it with minimal or even no human intervention. But modern software applications also use a wide range of third-party dependencies and often need to run on multiple operating systems and architectures. 

In this post, I will explain how the combination of Bazel and Testcontainers helps developers build and release software by providing a hermetic build system.

Using Bazel and Testcontainers together

Bazel is an open source build tool developed by Google to build and test multi-language, multi-platform projects. Several big IT companies have adopted monorepos for various reasons, such as:

Code sharing and reusability 

Cross-project refactoring 

Consistent builds and dependency management 

Versioning and release management

With its multi-language support and focus on reproducible builds, Bazel shines in building such monorepos.

A key concept of Bazel is hermeticity, which means that when all inputs are declared, the build system can know when an output needs to be rebuilt. This approach brings determinism where, given the same input source code and product configuration, it will always return the same output by isolating the build from changes to the host system.

Testcontainers is an open source framework for provisioning throwaway, on-demand containers for development and testing use cases. Testcontainers make it easy to work with databases, message brokers, web browsers, or just about anything that can run in a Docker container.

Using Bazel and Testcontainers together offers the following features:

Bazel can build projects using different programming languages like C, C++, Java, Go, Python, Node.js, etc.

Bazel can dynamically provision the isolated build/test environment with desired language versions.

Testcontainers can provision the required dependencies as Docker containers so that your test suite is self-contained. You don’t have to manually pre-provision the necessary services, such as databases, message brokers, and so on. 

All the test dependencies can be expressed through code using Testcontainers APIs, and you avoid the risk of breaking hermeticity by sharing such resources between tests.

Let’s see how we can use Bazel and Testcontainers to build and test a monorepo with modules using different languages.We are going to explore a monorepo with a customers module, which uses Java, and a products module, which uses Go. Both modules interact with relational databases (PostgreSQL) and use Testcontainers for testing.

Getting started with Bazel

To begin, let’s get familiar with Bazel’s basic concepts. The best way to install Bazel is by using Bazelisk. Follow the official installation instructions to install Bazelisk. Once it’s installed, you should be able to run the Bazelisk version and Bazel version commands:

$ brew install bazelisk
$ bazel version

Bazelisk version: v1.12.0
Build label: 7.0.0

Before you can build a project using Bazel, you need to set up its workspace. 

A workspace is a directory that holds your project’s source files and contains the following files:

The WORKSPACE.bazel file, which identifies the directory and its contents as a Bazel workspace and lives at the root of the project’s directory structure.

A MODULE.bazel file, which declares dependencies on Bazel plugins (called “rulesets”).

One or more BUILD (or BUILD.bazel) files, which describe the sources and dependencies for different parts of the project. A directory within the workspace that contains a BUILD file is a package.

In the simplest case, a MODULE.bazel file can be an empty file, and a BUILD file can contain one or more generic targets as follows:

genrule(
name = "foo",
outs = ["foo.txt"],
cmd_bash = "sleep 2 && echo 'Hello World' >$@",
)

genrule(
name = "bar",
outs = ["bar.txt"],
cmd_bash = "sleep 2 && echo 'Bye bye' >$@",
)

Here, we have two targets: foo and bar. Now we can build those targets using Bazel as follows:

$ bazel build //:foo <- runs only foo target, // indicates root workspace
$ bazel build //:bar <- runs only bar target
$ bazel build //… <- runs all targets

Configuring the Bazel build in a monorepo

We are going to explore using Bazel in the testcontainers-bazel-demo repository. This repository is a monorepo with a customers module using Java and a products module using Go. Its structure looks like the following:

testcontainers-bazel-demo
|____customers
| |____BUILD.bazel
| |____src
|____products
| |____go.mod
| |____go.sum
| |____repo.go
| |____repo_test.go
| |____BUILD.bazel
|____MODULE.bazel

Bazel uses different rules for building different types of projects. Bazel uses rules_java for building Java packages, rules_go for building Go packages, rules_python for building Python packages, etc.

We may also need to load additional rules providing additional features. For building Java packages, we may want to use external Maven dependencies and use JUnit 5 for running tests. In that case, we should load rules_jvm_external to be able to use Maven dependencies. 

We are going to use Bzlmod, the new external dependency subsystem, to load the external dependencies. In the MODULE.bazel file, we can load the additional rules_jvm_external and contrib_rules_jvm as follows:

bazel_dep(name = "contrib_rules_jvm", version = "0.21.4")
bazel_dep(name = "rules_jvm_external", version = "5.3")

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
name = "maven",
artifacts = [
"org.postgresql:postgresql:42.6.0",
"ch.qos.logback:logback-classic:1.4.6",
"org.testcontainers:postgresql:1.19.3",
"org.junit.platform:junit-platform-launcher:1.10.1",
"org.junit.platform:junit-platform-reporting:1.10.1",
"org.junit.jupiter:junit-jupiter-api:5.10.1",
"org.junit.jupiter:junit-jupiter-params:5.10.1",
"org.junit.jupiter:junit-jupiter-engine:5.10.1",
],
)
use_repo(maven, "maven")

Let’s understand the above configuration in the MODULE.bazel file:

We have loaded the rules_jvm_external rules from Bazel Central Registry and loaded extensions to use third-party Maven dependencies.

We have configured all our Java application dependencies using Maven coordinates in the maven.install artifacts configuration.

We are loading the contrib_rules_jvm rules that supports running JUnit 5 tests as a suite.

Now, we can run the @maven//:pin program to create a JSON lockfile of the transitive dependencies, in a format that rules_jvm_external can use later:

bazel run @maven//:pin

Rename the generated file rules_jvm_external~4.5~maven~maven_install.json to maven_install.json. Now update the MODULE.bazel to reflect that we pinned the dependencies.

Add a lock_file attribute to the maven.install() and update the use_repo call to also expose the unpinned_maven repository used to update the dependencies:

maven.install(

lock_file = "//:maven_install.json",
)

use_repo(maven, "maven", "unpinned_maven")

Now, when you update any dependencies, you can run the following command to update the lock file:

​​bazel run @unpinned_maven//:pin

Let’s configure our build targets in the customers/BUILD.bazel file, as follows:

load(
"@bazel_tools//tools/jdk:default_java_toolchain.bzl",
"default_java_toolchain", "DEFAULT_TOOLCHAIN_CONFIGURATION", "BASE_JDK9_JVM_OPTS", "DEFAULT_JAVACOPTS"
)

default_java_toolchain(
name = "repository_default_toolchain",
configuration = DEFAULT_TOOLCHAIN_CONFIGURATION,
java_runtime = "@bazel_tools//tools/jdk:remotejdk_17",
jvm_opts = BASE_JDK9_JVM_OPTS + ["–enable-preview"],
javacopts = DEFAULT_JAVACOPTS + ["–enable-preview"],
source_version = "17",
target_version = "17",
)

load("@rules_jvm_external//:defs.bzl", "artifact")
load("@contrib_rules_jvm//java:defs.bzl", "JUNIT5_DEPS", "java_test_suite")

java_library(
name = "customers-lib",
srcs = glob(["src/main/java/**/*.java"]),
deps = [
artifact("org.postgresql:postgresql"),
artifact("ch.qos.logback:logback-classic"),
],
)

java_library(
name = "customers-test-resources",
resources = glob(["src/test/resources/**/*"]),
)

java_test_suite(
name = "customers-lib-tests",
srcs = glob(["src/test/java/**/*.java"]),
runner = "junit5",
test_suffixes = [
"Test.java",
"Tests.java",
],
runtime_deps = JUNIT5_DEPS,
deps = [
":customers-lib",
":customers-test-resources",
artifact("org.junit.jupiter:junit-jupiter-api"),
artifact("org.junit.jupiter:junit-jupiter-params"),
artifact("org.testcontainers:postgresql"),
],
)

Let’s understand this BUILD configuration:

We have loaded default_java_toolchain and then configured the Java version to 17.

We have configured a java_library target with the name customers-lib that will build the production jar file.

We have defined a java_test_suite target with the name customers-lib-tests to define our test suite, which will execute all the tests. We also configured the dependencies on the other target customers-lib and external dependencies.

We also defined another target with the name customers-test-resources to add non-Java sources (e.g., logging config files) to our test suite target as a dependency.

In the customers package, we have a CustomerService class that stores and retrieves customer details in a PostgreSQL database. And we have CustomerServiceTest that tests CustomerService methods using Testcontainers. Take a look at the GitHub repository for the complete code.

Note: You can use Gazelle, which is a Bazel build file generator, to generate the BUILD.bazel files instead of manually writing them.

Running Testcontainers tests

For running Testcontainers tests, we need a Testcontainers-supported container runtime. Let’s assume you have a local Docker installed using Docker Desktop.

Now, with our Bazel build configuration, we are ready to build and test the customers package:

# to run all build targets of customers package
$ bazel build //customers/…

# to run a specific build target of customers package
$ bazel build //customers:customers-lib

# to run all test targets of customers package
$ bazel test //customers/…

# to run a specific test target of customers package
$ bazel test //customers:customers-lib-tests

When you run the build for the first time, it will take time to download the required dependencies and then execute the targets. But, if you try to build or test again without any code or configuration changes, Bazel will not re-run the build/test again and will show the cached result. Bazel has a powerful caching mechanism that will detect code changes and run only the targets that are necessary to run.

While using Testcontainers, you define the required dependencies as part of code using Docker image names along with tags, such as Postgres:16. So, unless you change the code (e.g., Docker image name or tag), Bazel will cache the test results.

Similarly, we can use rules_go and Gazelle for configuring Bazel build for Go packages. Take a look at the MODULE.bazel and products/BUILD.bazel files to learn more about configuring Bazel in a Go package.

As mentioned earlier, we need a Testcontainers-supported container runtime for running Testcontainers tests. Installing Docker on complex CI platforms might be challenging, and you might need to use a complex Docker-in-Docker setup. Additionally, some Docker images might not be compatible with the operating system architecture (e.g., Apple M1). 

Testcontainers Cloud solves these problems by eliminating the need to have Docker on the localhost or CI runners and run the containers on cloud VMs transparently.

Here is an example of running the Testcontainers tests using Bazel on Testcontainers Cloud using GitHub Actions:

name: CI

on:
push:
branches:
– '**'

jobs:
build:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v4

– name: Configure TestContainers cloud
uses: atomicjar/testcontainers-cloud-setup-action@main
with:
wait: true
token: ${{ secrets.TC_CLOUD_TOKEN }}

– name: Cache Bazel
uses: actions/cache@v3
with:
path: |
~/.cache/bazel
key: ${{ runner.os }}-bazel-${{ hashFiles('.bazelversion', '.bazelrc', 'WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel') }}
restore-keys: |
${{ runner.os }}-bazel-

– name: Build and Test
run: bazel test –test_output=all //…

GitHub Actions runners already come with Bazelisk installed, so we can use Bazel out of the box. We have configured the TC_CLOUD_TOKEN environment variable through Secrets and started the Testcontainers Cloud agent. If you check the build logs, you can see that the tests are executed using Testcontainers Cloud.

Summary

We have shown how to use the Bazel build system to build and test monorepos with multiple modules using different programming languages. Combined with Testcontainers, you can make the builds self-contained and hermetic.

Although Bazel and Testcontainers help us have a self-contained build, we need to take extra measures to make it a hermetic build: 

Bazel can be configured to use a specific version of SDK, such as JDK 17, Go 1.20, etc., so that builds always use the same version instead of what is installed on the host machine. 

For Testcontainers tests, using Docker tag latest for container dependencies may result in non-deterministic behavior. Also, some Docker image publishers override the existing images using the same tag. To make the build/test deterministic, always use the Docker image digest so that the builds and tests always use the exact same version of images that gives reproducible and hermetic builds.

Using Testcontainers Cloud for running Testcontainers tests reduces the complexity of Docker setup and gives a deterministic container runtime environment.

Visit the Testcontainers website to learn more, and get started with Testcontainers Cloud by creating a free account.

Learn more

Visit the Testcontainers website.

Get started with Testcontainers Cloud by creating a free account.

Get the latest release of Docker Desktop.

Vote on what’s next! Check out our public roadmap.

Have questions? The Docker community is here to help.

New to Docker? Get started.

Quelle: https://blog.docker.com/feed/