-
Notifications
You must be signed in to change notification settings - Fork 6
Migrating from n–ui to Page Kit
No apps use n-ui any more. This guide is included for posterity and doesn't necessarily reflect the current state of Page Kit.
- Preparation
- Remove n-ui
- Implement Page Kit Handlebars as Express view engine
- Set up basic build task for client-side JavaScript and Sass
- Set up basic integration with the Page Kit shell
- Implement the layout component with navigation data
- Set up Bower package resolution for Page Kit components
- Add the asset loader and provide styles and scripts to the shell
- Configure Page Kit JS code splitting
- Implement dom-loaded library to safely initialise all client-side JS code
- Integrate flags data with Page Kit shell component
- Integrate Page Kit app context data with the shell component
- Implement n-tracking (if the app requires it)
- Implement n-feedback (if the app requires it)
- Generate Lighthouse reports
- Clone your application from GitHub,
cd
into the directory - Install, build and run the unaltered branch to be sure it runs as expected. If it doesn't please raise appropriate bugfix PRs now.
- If everything is working as expected create a new
page-kit-migration
branch.
NOTE: This is quite a long step and you may need a notepad and pen.
- Delete
n-ui-build.config.js
in the app root directory. - In
.gitignore
, replace all thepublic/*
lines with a singlepublic/
line. - Remove
n-ui
scripts from themakefile
. We will add replacement scripts later. - Remove
n-ui
as a dependency frombower.json
andpackage.json
(but leaven-ui-foundations
, we'll still need that). - Now is a good time to bump some dependencies to versions which support Page Kit.
- Bump n-gage to
v4.0.0
or higher. - Bump n-heroku-tools to
v8.3.0
or higher.
- Bump n-gage to
-
Open the Sass entry point (usually client/main.scss) and replace
n-ui
withn-ui-foundations
, you may need to install this package as a Bower dependency.-@import 'n-ui/main'; +@import 'n-ui-foundations/mixins'
-
Remove any
nUiStylesheetStart/nUiStylesheetEnd
mixins from the Sass source code. There is no equivalent in Page Kit.
- Open the client-side JS entry point (usually
client/main.js
). - Comment out any references to
flags
imported fromn-ui
, we will bring them back later. - Comment out any references to
tracking
imported fromn-ui
, we will bring them back later. - Delete any dependency on
n-ui
or@financial-times/n-ui
. - Delete any references to
allstylesloaded
and hoist the contents of its callback into the parent scope. - Delete any
onAppInitialised()
calls.
-
Install
n-express
:npm install -S @financial-times/n-express
-
Open the application entry point (usually
server/app.js
) and replace the dependency on n-ui:- const express = require('@financial-times/n-ui'); + const express = require('@financial-times/n-express');
-
Update the Express server initialisation options. At a minimum add the
appName
,graphiteName
and setwithFlags
,withConsent
andwithAnonMiddleware
totrue
.const app = express({ + appName: 'application name', + graphiteName: 'application name', + withFlags: true, + withConsent: true, + withAnonMiddleware: true ... });
-
If any handlebars
helpers
are being registered, comment out these dependencies now and then remove thehelpers
option from the express server initialisation. -
Make a note of what features
app.locals.nUiConfig = {}
is configuring and then delete it.- The
preset
value will be eitherdiscrete
orcomplete
. Check the list of features included in each one here and make a note of the features being used in your application. -
Please note: Later we will need to initialise any client-side components that are not included in Page Kit (such as image lazy loading,
n-feedback
,o-date
, etc.)
- The
-
If the
.snyk
file contains a patch for n-ui, delete the patch now. -
Finally, search for any remaining references to
n-ui
(ornui
) and delete or comment them out. -
Commit your work with the message "Completely remove n-ui integration from this app"
NOTE: This is probably the hardest step and this will vary between applications depending on how it extends or works around the limitations of Handlebars.
-
Install the Handlebars package:
npm install -S @financial-times/dotcom-server-handlebars
-
In the application entry point (usually
server/app.js
), register Handlebars as an Express view engine and configure the template helpers:const { PageKitHandlebars, helpers } = require('@financial-times/dotcom-server-handlebars'); app.engine('.html', new PageKitHandlebars({ helpers }).engine);
If your app was using any additional Handlebars helpers (e.g
x-handlebars
) configure them now. -
Update all
response.render()
calls in the application's controllers to include the.html
file extension. If you miss anyresponse.render()
calls, you'll see an error like this:Error: No default engine was specified and no extension was provided.
-
Remove the
layout
property from the template data passed toresponse.render()
(usuallylayout: 'wrapper'
). -
If your application is using Handlebars directly (
require('handlebars')
):- Don't! Handlebars is a singleton ...
- ... and
n-ui
implemented a hack to load partial templates on application startup and append them to this. - If necessary, refactor the application to use a shared instance of
PageKitHandlebars
. You may prefer to add a newserver/handlebars-setup.js
module to achieve this.
-
Run the application and load it in your browser.
make build run-local
- This is the point in the migration where you will find out if your application is using any unsupported Handlebars helpers. For instance, Page Kit does not support
{{#defineBlock}}{{/defineBlock}}
as it uses a different mechanism for inserting content into the document<head>
and before/after the header and footer slots (using the layout options). Handlebars supports an inline partials mechanism which you can use instead if necessary. - Comment out the use of any other unsupported helpers for now and make a note of them.
It's important to get the application running and verify that it is delivering the expected HTML at this point as we will verify each of the following stages by running the application and checking it in the browser.
- This is the point in the migration where you will find out if your application is using any unsupported Handlebars helpers. For instance, Page Kit does not support
-
Commit your work and have a lovely cup of tea, you've earned it.
☺️
NOTE: This is probably the second hardest step and may vary between applications and the dependencies it uses.
-
Install the Page Kit build packages:
npm install -D \ @financial-times/dotcom-page-kit-cli \ @financial-times/dotcom-build-js \ @financial-times/dotcom-build-sass \ @financial-times/dotcom-build-bower-resolve
-
Create a
page-kit.config.js
file in the repository root:const path = require('path'); module.exports = { plugins: [ require('@financial-times/dotcom-build-js').plugin(), require('@financial-times/dotcom-build-sass').plugin(), require('@financial-times/dotcom-build-bower-resolve').plugin() ], settings: { build: { entry: { scripts: './client/main.js', styles: './client/main.scss' }, outputPath: path.resolve('./public') } } };
-
Configure Page Kit build steps in the application's
makefile
:build: + page-kit build --development build-production: + page-kit build watch: + page-kit build --development --watch
-
Build the application:
- There may be a number of warnings output to the console, inspect these but they can usually be ignored.
- If there are any errors resolve these now. In our tests the most common cause of problems is CJS/ESM interoperability.
- Open the
public/
folder and ensure the expected JS and CSS files are being generated along with amanifest.json
file.
It's important to get the application building correctly without any errors at this stage. If you are unsure run
make build-production
which will fail if any problems are found. -
Commit your work.
-
Install the shell package:
npm install -S @financial-times/dotcom-ui-shell
-
Install react, react-dom and the eslint react plugin (this is required by
n-gage
):npm install -S react react-dom \ && npm install -D eslint-plugin-react
-
Create a
page-kit-wrapper.js
file in the application's/server
directory with the following content:const React = require('react'); const ReactDOM = require('react-dom/server'); const { Shell } = require('@financial-times/dotcom-ui-shell'); module.exports = ({ response, next, shellProps }) => { return (error, html) => { if (error) { return next(error); } const document = React.createElement( Shell, { ...shellProps, contents: html } ); response.send('<!DOCTYPE html>' + ReactDOM.renderToStaticMarkup(document)); }; };
-
Integrate the new
page-kit-wrapper.js
module in the application's controller files.- Require the module.
- Create a
shellProps
object. - Create a
pageKitArgs
object passing in Express route handler params andshellProps
. - Pass the shell with its arguments as a third parameter to the
response.render()
call.
const pageKitWrapper = require('./server/page-kit-wrapper'); ... const shellProps = { pageTitle: 'application-title' }; const pageKitArgs = { request, response, next, shellProps }; ... response.render('layout.html', templateData, pageKitWrapper(pageKitArgs));
-
Add any additional properties that your application needs to
shellProps
alongsidepageTitle
, e.g.description
,openGraph
andjsonLd
. -
Build and run the application and check the output in the browser.
- The expected bootstrap scripts and meta tags should be present in the rendered HTML.
- The page should have a pink background.
-
Commit your work.
-
Install the layout component and the navigation middleware:
npm install -S \ @financial-times/dotcom-ui-layout \ @financial-times/dotcom-middleware-navigation
-
Require the navigation middleware in your application's server file and add it to the list of middlewares being used by your application.
+ const navigationMiddleware = require('@financial-times/dotcom-middleware-navigation'); ... app.use( + navigationMiddleware.init() ... );
-
If your app has subnavigation, add the
enableSubNavigation
option in theinit()
:+ navigationMiddleware.init({ enableSubNavigation: true }),
-
Integrate the layout component with your
page-kit-wrapper.js
module.- Require the module.
- Pass
layoutProps
to the existingdocument
component. - Update the
layoutProps
object withnavigationData
andheaderOptions
. - Add
layoutProps
to the function arguments.
+const { Layout } = require('@financial-times/dotcom-ui-layout'); ... -module.exports = ({ response, next, shellProps }) => { +module.exports = ({ response, next, shellProps, layoutProps }) => { + layoutProps.navigationData = response.locals.navigation; + layoutProps.headerOptions = { ...response.locals.anon}; ... const document = React.createElement( Shell, - { ...shellProps, contents: html } + { ...shellProps }, + React.createElement(Layout, { ...layoutProps, contents: html }) ); };
-
Build and run the application and check the output in the browser.
- The header, footer and navigation elements should be present in the rendered html.
- Site skip-links should be present in the rendered html.
-
Commit your work.
-
Install the bower glob resolver package as a devDependency:
npm install -D bower-glob-resolver
-
Add the new resolver to the application's
.bowerrc
:{ "resolvers": ["bower-glob-resolver"] }
-
Use the resolver to add the Page Kit UI components as bower dependencies in the
bower.json
file:"dependencies": { ... + "page-kit-ui-components": "glob:node_modules/@financial-times/dotcom-ui-*/bower.json" }
-
Initialise the layout component in the application's client-side JS entrypoint:
import * as pageKitLayout from '@financial-times/dotcom-ui-layout' pageKitLayout.init();
-
Include the layout component styles in the application's CSS entrypoint (this should be the first line):
@import '@financial-times/dotcom-ui-layout/styles';
-
Delete and reinstall the
bower_components
directory. -
Build the application.
- If you see errors declaring missing bower modules, these have probably been supplied via
n-ui
in the past, install them directly in your application now. - Delete and reinstall the
bower_components
directory.
- If you see errors declaring missing bower modules, these have probably been supplied via
-
Commit your work.
-
Install the assets middleware:
npm install -S @financial-times/dotcom-middleware-assets
-
Integrate the assets middleware in the application's server file.
-
Add the middleware to the list of middlewares being used by your application and configure the
hostStaticAssets
andpublicPath
options. -
N.B.
[application-name]
(used for the non-isProduction
publicPath
value) will likely not be prepended withft-
ornext-
, i.e.product
rather thannext-product
.+ const assetsMiddleware = require('@financial-times/dotcom-middleware-assets'); ... + const isProduction = process.env.NODE_ENV === 'production'; ... app.use( + assetsMiddleware.init({ + hostStaticAssets: !isProduction, + publicPath: isProduction ? '/__assets/hashed/page-kit' : '/__dev/assets/[application-name]' + }) );
-
-
Use the assets loader to add
scripts
andstylesheets
properties to the existingshellProps
object. (Note: The values forentrypointJS
andentrypointCSS
can be found inpage-kit.config.js
)const shellProps = { + scripts: response.locals.assets.loader.getScriptURLsFor('scripts'), + stylesheets: response.locals.assets.loader.getStylesheetURLsFor('styles') ... };
-
Build and run the application and check the output in the browser.
- The network tab should show the expected requests for script files and stylesheets.
- The header and footer elements should be styled.
- The application's client-side behaviour should work. Please note you may find runtime errors with the compiled JS. The most common issues are caused by ESM and Common JS module syntax being mixed up or otherwise used incorrectly. You should fix these issues in a separate commit.
-
Commit your work.
-
Install the code splitting package:
npm install -D @financial-times/dotcom-build-code-splitting
-
Add the code splitting plugin to the list of plugins in
page-kit.config.js
:plugins: [ + require('@financial-times/dotcom-build-code-splitting').plugin() ... ]
-
Build and run the application and check the output in the browser.
- Do the client-side assets load correctly?
- Check the
/public
file, it should contain several separate assets files.
-
Commit your work.
- Install dom-loaded:
npm install -D dom-loaded
- Implement dom-loaded in the client-side JS:
+import domLoaded from 'dom-loaded'; import * as pageKitLayout from '@financial-times/dotcom-ui-layout'; +domLoaded.then(() => { pageKitLayout.init(); ... +});
- Build and run the application and check the output in the browser.
- Client-side JS should be running
- The drawer menu and search bar icons should show/hide those elements.
- Commit your work.
- Install the flags package:
npm install -S @financial-times/dotcom-ui-flags
- Implement the flags component in the client-side JS:
import domLoaded from 'dom-loaded'; import * as pageKitLayout from '@financial-times/dotcom-ui-layout'; +import * as pageKitFlags from '@financial-times/dotcom-ui-flags'; domLoaded.then(() => { + const flags = pageKitFlags.init(); pageKitLayout.init(); ... });
- Add a
flags
property toshellProps
:const shellProps = { + flags: response.locals.flags, ... };
- Find any commented out uses of flags and reinstate them.
- Find any code relating to Easter eggs and delete it entirely now or in a separate commit.
- Build and run the application and check the output in the browser.
- The flags script should be populated with the relevant flags data.
- Commit your work.
You may not need this step if app.locals.nUiConfig
does not have tracking or ads. You will probably still need it if you have flags.
- Install the app context packages:
npm install \ @financial-times/dotcom-ui-app-context \ @financial-times/dotcom-middleware-app-context
- Require the app context middleware and add it to the list of middlewares used in the application server file.
+const appContextMiddleware = require('@financial-times/dotcom-middleware-app-context'); ... app.use( + appContextMiddleware.init() ... );
- Implement the app context component in the client-side JS:
import domLoaded from 'dom-loaded'; import * as pageKitLayout from '@financial-times/dotcom-ui-layout'; import * as pageKitFlags from '@financial-times/dotcom-ui-flags'; +import * as pageKitAppContext from '@financial-times/dotcom-ui-app-context'; domLoaded.then(() => { const flags = pageKitFlags.init(); + const appContext = pageKitAppContext.init(); pageKitLayout.init(); ... });
- Add an
appContext
property toshellProps
:const shellProps = { + appContext: response.locals.appContext.getAll(), ... };
- Build and run the application and check the output in the browser.
- The app context script should be populated with the relevant context data.
- Commit your work.
- Install n-tracking using npm:
npm install -S @financial-times/n-tracking
- Add the fallback tracking to the layout component in
page-kit-wrapper.js
providing that the flag is enabled:const { CoreTracking } = require('@financial-times/n-tracking'); ... const flags = response.locals.flags; ... if (flags && flags.oTracking) { layoutProps.footerAfter = React.createElement(CoreTracking, { appContext: response.locals.appContext.getAll() }); }
- Initialise
n-tracking
in the client-side JS. At a minimum, include the app context as an option:import domLoaded from 'dom-loaded'; import * as pageKitLayout from '@financial-times/dotcom-ui-layout'; import * as pageKitFlags from '@financial-times/dotcom-ui-flags'; import * as pageKitAppContext from '@financial-times/dotcom-ui-app-context'; +import * as nTracking from '@financial-times/n-tracking'; domLoaded.then(() => { const flags = pageKitFlags.init(); const appContext = pageKitAppContext.init(); pageKitLayout.init(); + if (flags.get('oTracking')) { + nTracking.init({ appContext: appContext.getAll() }); + } ... });
- Find any commented out uses of tracking.
- Decide which uses of tracking are still being actively used or monitored. Check for any tracking documents and Chartio dashboards or speak to the data team.
- Reinstate any tracking which has been verified as still in use.
- Build and run the application and check the output in the browser.
- A
financial-times-o-tracking.bundle.js
should have been built. - Tracking events to
spoor-api.ft.com
should be present in the network tab. - Compare the contents of a page view event between production and your locally running app.
- A
- Commit your work.
- Install
n-feedback
as a Bower dependency:bower i -S n-feedback
- Add the placeholder element to the layout component providing that the flag is enabled:
if (flags && flags.qualtrics) { layoutProps.footerBefore = '<div class="n-feedback__container n-feedback--hidden"></div>'; }
- Initialise the client-side JS:
import domLoaded from 'dom-loaded'; import * as pageKitLayout from '@financial-times/dotcom-ui-layout'; import * as pageKitFlags from '@financial-times/dotcom-ui-flags'; import * as pageKitAppContext from '@financial-times/dotcom-ui-app-context'; import * as nTracking from '@financial-times/n-tracking'; +import * as nFeedback from 'n-feedback'; domLoaded.then(() => { const flags = pageKitFlags.init(); const appContext = pageKitAppContext.init(); pageKitLayout.init(); if (flags.get('oTracking')) { nTracking.init({ appContext: appContext.getAll() }); } + if (flags.get('qualtrics')) { + // NOTE: n-feedback mutates the configuration object passed to it but + // the app context object is frozen and immutable and must be cloned. + nFeedback.init({ ...appContext.getAll() }); + } ... });
- Import the n-feedback UI style in the application's client-side styles entrypoint:
@import 'n-feedback/main'; // n-feedback depends upon styles from o-overlay, but doesn't explicitly so we have to include o-overlay $o-overlay-is-silent: false; @import 'o-overlay/main';
- Build and run the application and check the output in the browser.
- A teal feedback button should be present in the bottom right corner of the viewport. If you click it, an overlay should show a questionnaire.
- Commit your work.
NOTE: This is a pre-requisite to merging your pull request to production.
- Visit https://web.dev/measure.
- Add the url for your application and generate a report.
- Generate a
.png
of the compiled report using native browser tools or the [Full Page Screen Capture extension] (chrome://extensions/?id=fdpohaocaechififmbbbbbknoalclacl). - Add a comment to your pull request with the report showing page performance when the application uses n-ui.
- Also add the report to our shared drive location with a sensible file name.
- Once your code is merged, repeat these steps with a report showing page performance when the application uses Page Kit.