Upgrading Brightspot Styleguide
Note: This guide covers the upgrade of Brightspot Styleguide to version 4.2.22 or later.
Goals
- Remove gulp in favor of webpack
- Faster start-up
- Handlebars rendering in browser
Latest Version
The latest version of styleguide can be found on NPM
Preparation
Before actually upgrading the packages, start preparing some of the items that need be changed. These items are things such as disabling non updated themes in orded to work through the themes one at a time, as well as updates to the various linting configurations in order to align with the newer practices, and even stuff like not including zip bundle in project.
Tell git to ignore Zip bundle in gitignore
In your project .gitignore
add an entry to ignore *.zip
.gitignore
*.zip
build.gradle
In your project root directory’s build.gradle
file:
Look for the line ext.brightspotGradlePluginVersion =
and upgrade version to match ext.brightspotGradlePluginVersion = '4.2.12'
Look for the node version and update node as well as the yarn version.
node {
version = '18.7.0'
yarnVersion = '1.22.19'
download = true
}
Disable some themes (if needed), in order to update them one at a time
In your project’s root directory, look for the file settings.gradle
and in that file look for the lines which include and build your themes. These lines look like include(':myproject-theme-themename')
& project(':myproject-theme-themename').projectDir = file('themes/myproject-theme-themename')
where myproject-theme-themename
is really the name and folder of your theme.
So now with that in mind, comment out those lines for the “all” themes (which are not already updated), and leave only the theme you are updating at the moment uncommented (and any which you’ve already updated). The goal here is to update the themes one at a time such that we can actually build the project, before moving to the next theme. Part of the reason is the old and new themes may need different versions of Node.
Disable some themes (again, just like above step, but this time in the file site/build.gradle
)
Similar to the above steps in this file you’ll see lines such as api project(':myproject-theme-themename')
.npmrc files
Remove them. At least with the typical packages in modern brightspot you should no longer need this file Not much info to explain. Literally delete or comment out the contents of file. This applies to both the one in root as well as the one in each theme.
Updates to package.json
This is the last real part of preparing the upgrade. In some ways this step IS the upgrade.
PLEASE NOTE: updating the node version and dependencies may affect other dependencies specific to your site not covered in this upgrade. Please test accordingly
- In the following diff do observe that we are updating various modules. There are some things I want to highlight:
- Brightspot styleguide version is newer, which of course is the main bit of this process. Remove the
brightspot-styleguide
dependency and add@brightspot/styleguide
with the version wanted - We added
@babel/eslint-parser
and removed the older equivalent - We added the
copy-webpack-plugin
which is whats helping us achieve the copy tasks previously done ingulp
- We removed gulp. It is no longer needed (your project could vary, but ideally if you have any tasks that need gulp, the advice would be to convert those to webpack as well).
- (Optional) We added web components package for loading custom elements, which is replacing a commonly used hardcoded polyfill in each theme’s respective Page-head.hbs.
- Navigate to your theme’s Page-head.hbs and look for this script:
<!--This is needed for custom elements to function in browsers that support them natively but that are using es6 code transpiled to es5. This will cause a non-fatal error to show up in the IE11 console. It can be safely ignored. https://github.com/webcomponents/webcomponentsjs/issues/749 --> <script> (function () { 'use strict'; (()=>{'use strict';if(!window.customElements)return;const a=window.HTMLElement,b=window.customElements.define,c=window.customElements.get,d=new Map,e=new Map;let f=!1,g=!1;window.HTMLElement=function(){if(!f){const a=d.get(this.constructor),b=c.call(window.customElements,a);g=!0;const e=new b;return e}f=!1;},window.HTMLElement.prototype=a.prototype;Object.defineProperty(window,'customElements',{value:window.customElements,configurable:!0,writable:!0}),Object.defineProperty(window.customElements,'define',{value:(c,h)=>{const i=h.prototype,j=class extends a{constructor(){super(),Object.setPrototypeOf(this,i),g||(f=!0,h.call(this)),g=!1;}},k=j.prototype;j.observedAttributes=h.observedAttributes,k.connectedCallback=i.connectedCallback,k.disconnectedCallback=i.disconnectedCallback,k.attributeChangedCallback=i.attributeChangedCallback,k.adoptedCallback=i.adoptedCallback,d.set(h,c),e.set(c,h),b.call(window.customElements,c,j);},configurable:!0,writable:!0}),Object.defineProperty(window.customElements,'get',{value:(a)=>e.get(a),configurable:!0,writable:!0});})(); /** @license Copyright (c) 2017 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ }()); </script>
- Delete all contents of this script block and replace it with:
<script src="{{cdn "/webcomponents-loader/webcomponents-loader.js"}}"></script>
- Next, add this dependency in your theme’s package.json:
"@webcomponents/webcomponentsjs": "2.5.0"
- Then, navigate to your theme’s
webpack.common.js
and add the following Plugin. There may be other patterns present, keep them and add this one as well.plugins: [ new CopyPlugin({ patterns: [ { from: 'node_modules/@webcomponents/webcomponentsjs/bundles/*.js', to: 'webcomponents-loader/bundles/[name][ext]' }, { from: 'node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js', to: 'webcomponents-loader/' } ] }) ],
-
All done! This may not be applicable to your project, but if it is, it’s worth updating as it’s easier to keep that up to date this way vs a hardcoded script. One way to know if it’s applicable to a project is if you see a similar console error as below that occurs as a result of the custom script: (Instead of
Banner
it may say something else)-
Uncaught TypeError: Class constructor Banner cannot be invoked without 'new' at new j (new-gallery:316:613) at CustomElementRegistry.value (new-gallery:316:889) at registerCustomElements (All.min.d60fa7e791797bfff55f465499097ec6.gz.js:12272:18)
-
- LAST BUT ALSO MOST IMPORTANT The
scripts
portion was all updated to use the new webpack configs and more. Replace your scripts with these new ones.- You no longer need to use gulp.js at all, so you can use webpack-dev-server directly to start the local Styleguide server
{ "scripts": { "server:styleguide": "webpack serve --config webpack.dev.js" } }
- And same goes for creating the theme bundle:
{ "scripts": { "build": "yarn run eslint \"styleguide/**/*.js\" && webpack --config webpack.prod.js && node node_modules/@brightspot/styleguide/build/bundle -b build/styleguide -o build/theme.zip", } }
- You no longer need to use gulp.js at all, so you can use webpack-dev-server directly to start the local Styleguide server
- Brightspot styleguide version is newer, which of course is the main bit of this process. Remove the
package.json example:
{
"name": "site-theme-default",
"version": "1.0.0-SNAPSHOT",
"private": true,
"license": "UNLICENSED",
"engines": {
"node": "18.7.0"
}
"devDependencies": {
"@babel/core": "7.18.10",
"@babel/eslint-parser": "7.18.9",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/plugin-transform-classes": "7.18.9",
"@babel/preset-env": "7.18.10",
"@brightspot/styleguide": "4.7.8",
"autoprefixer": "10.4.8",
"babel-loader": "8.2.5",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.7.1",
"cssnano": "5.1.12",
"eslint": "8.21.0",
"eslint-config-prettier": "8.5.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-n": "15.2.4",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-react": "7.30.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-webpack-plugin": "3.2.0",
"file-loader": "6.2.0",
"fs-extra": "1.0.0",
"graphql-tag": "2.12.6",
"less": "4.1.3",
"less-loader": "11.0.0",
"mini-css-extract-plugin": "2.6.1",
"postcss": "8.4.16",
"postcss-loader": "7.0.1",
"prettier": "2.7.1",
"style-loader": "3.3.1",
"url-loader": "4.1.1",
"webpack": "5.74.0",
"webpack-bundle-analyzer": "4.5.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "3.11.3",
"webpack-merge": "5.8.0"
},
"dependencies": {
"@babel/helper-get-function-arity": "7.16.7",
"@babel/runtime": "7.18.9",
"picturefill": "3.0.3",
"sanitize.css": "13.0.0"
},
"scripts": {
"build": "yarn run eslint \"styleguide/**/*.js\" && webpack --config webpack.prod.js && node node_modules/@brightspot/styleguide/build/bundle -b build/styleguide -o build/theme.zip",
"format": "yarn run eslint \"styleguide/**/*.js\" --fix",
"profile": "webpack serve --config webpack.profile.js",
"server:styleguide": "webpack serve --config webpack.dev.js"
}
}
Installing the packages
- Once all the above its done we are now ready to install the update.
- Make sure you are on AT LEAST node version 12
- On your theme directory run
yarn install
(If you encounter any issues you can delete thenode_modules
dir as well asyarn.lock
file, and runyarn cache clean
and try again… It’s a clean start in a sense)
.eslintrc - Configure this file to use some new things
- Here, change the line
"parser": "babel-eslint"
to the line"parser": "@babel/eslint-parser"
as that will be the new package. - Make a mental note that we may need to add some rules to change some linting rules if we face some difficult to update errors when linting. Providing an example of the
.eslintrc
configurations you’ll need to update. That’s not the whole file, it’s just an example highlighting what you’ll want to change.
.eslintrc example:
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"parser": "@babel/eslint-parser",
"parserOptions": {
"allowImportExportEverywhere": true,
"sourceType": "module",
"requireConfigFile": false
}
}
.nvmrc - Create one as ideally you should be using NVM for management of Node
- In the root of each theme directory, create a file called
.nvmrc
- In
package.json
, note the version of Node you are using. It will look something like this:
"engines": {
"node": "18.14.2"
},
- Copy that value into
.nvmrc
. This will define the Node version your theme is using.
v18.14.2
.gulpfile - IMPORTANT: Take note of what’s in the gulpfile. We want to remove it in its entirety, but some tasks may need to be moved into webpack.
- This upgrade step list doesnt cover the scope of a potential upgrade with some extremely unique and custom gulp task that may need to be maintained. The reason is customizations could be an infinite variety of things.
- Some common tasks in gulp to be moved to webpack are tasks related to moving an assets folder. Often times we have found such task relating to directory
assets|static
. The webpack copy plugin is the solution should you encounter such a case.
plugins: [
new CopyPlugin({
patterns: [
{
from: '../../styleguide/svgs',
to: 'svgs/',
},
{
from: 'styleguide/assets',
to: 'styleguide/assets',
}
],
})
],
ABOVE is an example which (1) copies an svg
folder from an outside styleguide into the bundle & (2) copies an assets folder into the bundle following the same dir strucutre
- Additionally there may be tasks related to
AMP
. These taks usually run anAmp.less
file and convert it into an includable.hbs
file. If you encounter this one you will likely need to achieve the same (but within webpack) by creating anAmp.js
file and importingAmp.less
in it (similar to how All.js imports All.js). Once that is done (in the same manner the All.js works), you’ll need to add the corresponding entry or entries in the webpack common file as well as webpack prod.// In webpack prod plugins: [ new MiniCssExtractPlugin({ filename: (pathData) => { if (pathData.chunk.name === 'styleguide/Amp.min.js') { return pathData.chunk.name.replace('.js', '') + '.css.amp.hbs' } else { return pathData.chunk.name.replace('.js', '') + '.css' } }, }), ],
Above is a snippet for use in the plugins configuration in webpack prod file.
The new Styleguide breaks the theme compatibility checker
It does far less processing on your example files (i.e. you now need to set both _template and _styledTemplate if you want to display one of the style variations). To fix this, you can run: npx node ./node_modules/@brightspot/styleguide/build/upgrade
Theme _config.json - A bit of cleanup
The lines "sources": [],
& ` “vars”: {},` were empty. Remove them. This isn’t really part of the upgrade but take the opportunity to clean that up by removing those lines.
The webpack files in your theme directory
- There should be 4 additional files going forward in your theme directory. If they already exist within your theme directory make any changes specified in the next few sections.
webpack.profile.js
- this one may be new to your project. Copy and paste the file below with the same name in your theme`s folderwebpack.dev.js
webpack.common.js
webpack.prod.js
The changes we need on these webpack files
- For the
webpack.profile.js
file as the old projects are likely missing it, simply add it. A copy of it is provided herein.
webpack.profile.js example:
const { merge } = require('webpack-merge')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin
module.exports = merge(require('./webpack.prod.js'), {
plugins: [new BundleAnalyzerPlugin()]
})
- For the
webpack.dev.js
file, make the following tweaks - Observe the
styleguide
variable is now lowercase and the import is different. - Observe the
merge
import is now written as a non default named import hence the{
around it. - Moved the common
require
statement to the top for clarity. - The line
Styleguide.webpackDevServerConfig(webpack, {
is nowstyleguide.webpack('./styleguide', webpack, {
- Remember those tasks from gulp I mentioned should be commented out? Look at the use of the copy plugin herein. That does the same copying into the bundle we were previously using webpack for.
webpack.dev.js example:
const styleguide = require('@brightspot/styleguide')
const webpack = require('webpack')
const { merge } = require('webpack-merge')
module.exports = merge(
require('./webpack.common.js'),
styleguide.webpack('./styleguide', webpack, {
mode: 'development',
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'less-loader'
}
]
}
]
}
})
)
- For the
webpack.prod.js
file, make the following tweaks:- Same as above for some of the var names.
webpack.prod.js example:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const { merge } = require('webpack-merge')
const entryLessFile = path.resolve(__dirname, './styleguide/All.less')
module.exports = merge(require('./webpack.common.js'), {
mode: 'production',
output: {
chunkFilename: '[name].[contenthash].js'
},
plugins: [
new MiniCssExtractPlugin({
filename: 'styleguide/All.min.css'
})
],
module: {
rules: [
{
test: entryLessFile,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'less-loader'
]
}
]
}
})
- Additionally now we have a new change to how we extract the CSS into files. Observe also there are some special lines to do the CSS conversion into an HBS file just like we were doing previously for amp. This takes care that portion we were previously handling via
gulp
(now in webpack)
Example:
plugins: [
new MiniCssExtractPlugin({
filename: pathData => {
if (
pathData.chunk.name === 'styles/amp/Amp.js' ||
pathData.chunk.name === 'newsletter/NewsletterInline.js' ||
pathData.chunk.name === 'newsletter/NewsletterEmbed.js'
) {
return pathData.chunk.name.replace('.js', '') + '.css.hbs'
} else {
return pathData.chunk.name.replace('.js', '') + '.css'
}
}
})
],
- For the
webpack.common.js
file, make the following tweaks:- You’ll also need to explicitly set the webpack output directory
module.exports = { output: { path: path.resolve(__dirname, './build/styleguide'),
- The main additions here is we remove
eslint-loader
and instead, inplugins
run thecopy and eslint
plugins, to imitate those that setup gulp was doing previously where it was moving the asset directories and such.
- You’ll also need to explicitly set the webpack output directory
webpack.common.js example:
const CopyPlugin = require('copy-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const path = require('path')
module.exports = {
entry: {
'styleguide/All.min.js': './styleguide/All.js',
'styleguide/util/IEPolyfills.js': './styleguide/util/IEPolyfills.js'
},
output: {
path: path.resolve(__dirname, './build/styleguide'),
filename: '[name]',
chunkFilename: 'styleguide/chunk/[name].[contenthash].js',
publicPath: '/'
},
plugins: [
new CopyPlugin({
patterns: [
{ from: 'styleguide/PreviewPage.css', to: 'styleguide' },
{ from: 'styleguide/assets/**', to: '.' }
]
}),
new ESLintPlugin()
],
module: {
rules: [
// Split out large binary files into separate chunks.
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource'
},
// Transpile JS.
{
test: /\.js$/,
exclude: /node_modules/,
use: ['babel-loader']
}
]
},
Prepare the styleguides menu (previously _pages.config.json
)
- Look for
_pages.config.json
and rename it to_navigation.config.json
- Other than the name it needs some minor tweaks. Observe the changes with the following before and after.
- The top level
pages
should now benavigation
- The
html
extension is now thejson
extension just like the files in styleguide - The key
name
is nowdisplayName
- The key
content
is nowpage
- Also use explicit example rather than any wildcards (not shown in example below however) BEFORE
{ "pages":[ { "name":"Globals", "pages":[ { "name":"Typography", "content":"/_resource/TypographyPage.html" } ] } ] }
AFTER
{ "navigation":[ { "displayName":"Globals", "pages":[ { "displayName":"Typography", "page":"/_resource/TypographyPage.json" } ] } ] }
- The top level
Running the (UPGRADED) styleguide
We’ve updated the dependencies, but some things will still need to be updated in the FE templates and js.
Run the styleguide.
Run it using yarn server:styleguide
in your terminal while in the theme directory (this is that script in the package.json).
- The build process should be much faster now, and visit localhost:8080 on your browser.
- Unless you are super lucky the expectation is now you should see styleguide starting to run, but… (onto next section)
What to expect
Now you are likely seeing some errors. If all things are going as expected, these are lots of js errors, in your terminal regarding linting rules. The quickest way to get through these is:
- Run
yarn format
to have the formatted quickly format your js based on its expectations. - You may still have some errors that need to be manually updated. Go ahead and do so.
- Additionally you can go back to the
.eslintrc
file, and add rules, should there be some rules that are easier fixed by disabling some lint rules (example of such rules provided)
The end
Once all the above steps have been done, styleguide should be rendering fine.
Potential gotchas
- Your project may contain empty string values within certain
_config
files i.e.""
. Null strings are no longer accepted in 4.5. If your project throws errors like/fields/blogPageStyle/values/0/value: Should be at least 1 characters
you should search your the pertinent files for these null strings. To resolve this issue, we recommend to remove the Key/Value pair that threw the error and place the key into the field,cms.ui.placeholder
. - In ImageSizes.config, an error I found in some projects is mispelling keys in the image sizes such as
previewwidth
which should bepreviewWidth
now throws an error. - In the new
_navigation.config.json
thehideTabs
throws error. I’ve removed those entries - In projects that were using
displayName
to rename a content type (the actual content or module, and not a style) that now throws an error. Removing such usages ofdisplayName
. - If you are running styleguide before completing the Gradle Plugin Upgrade step, please ensure that
brightspotGradlePluginVersion
, located within/build.gradle
is set to at least4.2.9
. Failing to do so may cause errors such aszsh: command not found: webpack
orUnknown argument: build
. These errors are caused by changes in the way yarn processes commands changed in 4.2.9. - You may encounter issues with handlebar helpers such as rendering errors for example pages after running
yarn server:styleguide
, inspect elements and within the console look forindex.ts:825 ReferenceError: java is not defined
. This error may be caused by code in yourstyleguide/_helpers.js
within your theme folder that looks like this:Handlebars.registerHelper('svgPlaceholder', function (width, height) { const svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="' + (parseInt(height, 10) || 1) + 'px" width="' + (parseInt(width, 10) || 1) + 'px"></svg>' if (typeof Buffer !== 'undefined') { return 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64') } else { return ( 'data:image/svg+xml;base64,' + java.util.Base64.encoder.encodeToString( svg.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8) ) ) } })
The fix is to add an else if
like so:
Handlebars.registerHelper('svgPlaceholder', function (width, height) {
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="' +
(parseInt(height, 10) || 1) +
'px" width="' +
(parseInt(width, 10) || 1) +
'px"></svg>'
if (typeof Buffer !== 'undefined') {
return 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64')
} else if (typeof btoa !== 'undefined') {
return 'data:image/svg+xml;base64,' + btoa(svg)
} else {
return (
'data:image/svg+xml;base64,' +
java.util.Base64.encoder.encodeToString(
svg.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)
)
)
}
})