Skip to content
This repository has been archived by the owner on Apr 1, 2020. It is now read-only.

Commit

Permalink
V7 (#1101)
Browse files Browse the repository at this point in the history
* remove tooltip
 🐿 v2.5.16

* remove o-header subnav
 🐿 v2.5.16

* main-without-n-ui.js -> main.js & force some hings on
 🐿 v2.5.16

* pass n-ui-config imperitively
 🐿 v2.5.16

* better namespacing of bootstrapping stuff
 🐿 v2.5.16

* the rijiggening
 🐿 v2.5.16

* update demo to rejiggeninig
 🐿 v2.5.16

* link
 🐿 v2.5.16

* linting and all that
 🐿 v2.5.16

* slight improvement of initializer
 🐿 v2.5.16

* loadsa tests
 🐿 v2.5.16

* stop providing a stylesheet
 🐿 v2.5.16

* start working towards not building n-ui styles in app
 🐿 v2.5.16

* some way to getting the demo app working
 🐿 v2.5.16

* stop bloody brotlifying the bloody darn thing
 🐿 v2.5.16

* more buildy stuff
 🐿 v2.5.16

* revert stylesheet changes... for nooo
 🐿 v2.5.16

* all js builds exclude n-ui
 🐿 v2.5.16

* docs
 🐿 v2.5.16

* fixing more tests
 🐿 v2.5.16

* now that's waht I call a test suite
 🐿 v2.5.16

* fixed demo app
 🐿 v2.5.16

* de-preactify
 🐿 v2.5.16

* lint
 🐿 v2.5.16

* fix build prod
 🐿 v2.5.16

* minor fixes
 🐿 v2.5.16

* some fixes
 🐿 v2.5.16

* lots of tidying and fixing
 🐿 v2.5.16

* fix crazy webpack sourcemap paths?
 🐿 v2.5.16

* doc tweaks
 🐿 v2.5.16

* no more circular mindfuck (#1107)

* no more circular mindfuck
 🐿 v2.5.16

* Update main.js

* expose a few more externals
 🐿 v2.5.16

* Dora/v7 error handling (#1111)

* add fatal script flags and basic load error handling
 🐿 v2.5.16

* bump webpack
 🐿 v2.5.16

* being pedantic
 🐿 v2.5.16

* more pedantic
 🐿 v2.5.16

* use Array.map correctly
 🐿 v2.5.16

* lint
 🐿 v2.5.16

* better naming and error handling
 🐿 v2.5.16

* guard for the existence of onerror
 🐿 v2.5.16

* undo stupid
 🐿 v2.5.16

* experiment: onload and onerror complexity
 🐿 v2.5.16

* remove complexity
 🐿 v2.5.16

* experiment: is it onerror?
 🐿 v2.5.16

* no empty arguments
 🐿 v2.5.16

* Add deferred scripts to ctm (#1110)

* turn off o-errors when a script fails to load (#1113)

* turn off o-errors when a script fails to load
 🐿 v2.5.16

* log to spoor
 🐿 v2.5.16

* careful - es5 only inline
 🐿 v2.5.16

* verbose names... what's a few bytes between friends
 🐿 v2.5.16

* simplify loadScript
 🐿 v2.5.16

* Update README.md
  • Loading branch information
wheresrhys committed Oct 23, 2017
1 parent 02ea5fd commit 1b86a6e
Show file tree
Hide file tree
Showing 54 changed files with 808 additions and 1,127 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
/public/__about.json
/public/head-n-ui-core.css
/public/main.css
/public/main-without-n-ui.js
/public/main-without-n-ui.js.map
/public/main.js
/public/main.js.map
/public/n-ui
key.pem
key-cert.pem
Expand Down
94 changes: 25 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ n-ui is a wrapper around n-express which adds templating and asset loading featu
```
const app = require('@financial-times/n-ui')(opts)
app.locals.nUiConfig = {
preset: 'complete', // 'discrete' will turn off ads & various popups
features: {
lazyLoadImages: true // turns individual features on/off. Check `/browser/bootstrap/js/component-initializer.js` for an up to date feature list
}
}
```

Where opts is an object supporting all `n-express`'s options, but with many set to `true` by default (see `/server/index.js` for details). Additioanl options include
Expand Down Expand Up @@ -44,37 +50,30 @@ To define entry points for your assets use a `n-ui-build.config.js` file in the

### JS

#### Config
This should live in a `/client/n-ui-config.js` file

```javascript
module.exports = {
preset: 'complete', // 'discrete' will turn off ads & various popups
features: {
lazyLoadImages: true // turns individual features on/off. Check `/browser/bootstrap/js/component-initializer.js` for an upto date feature list
}
}
```

#### App bootstrapping

n-ui takes care of loading polyfills etc, and your application code shoudl be wrapped in the bootstrap method
n-ui takes care of loading polyfills etc, in order. n-ui exports 4 things you'll want to use
- `flags` - the feature/development/maintenance/MVT flags object
- `appInfo` - metadata about the app that's serving the page
- `allStylesLoaded` - a promise that resolves once all the lazy-loaded styles are in place
- `onAppInitialized` [required] - a function to call once the app js has successfully executed. This tells integration tests when the page is 'complete' among other things

`import` one or more of the above from n-ui in your application code, which no longer needs to be wrapped in a function

e.g.

```javascript
import { bootstrap } from 'n-ui';

bootstrap(({ flags , appInfo, allStylesLoaded }) => {
if (flags.get('feature')) {
component.init();
}

allStylesLoaded
.then(() => {
lazyComponent.init();
});
});
import { flags , allStylesLoaded, onAppInitialized } from 'n-ui';

if (flags.get('feature')) {
component.init();
}

allStylesLoaded
.then(() => {
lazyComponent.init();
onAppInitialized(); // it's up to you to define when your app is 'ready'
});
```

### Sass
Expand Down Expand Up @@ -112,7 +111,7 @@ When you release an n-ui tag 3 things happen
- the npm package is published
- during work hours (9am to 4pm), all user-facing apps are rebuilt to pick up the changes

## APIs
## Server side APIs

### Linked Resources (preload) `res.linkResource(url, meta, options)`
Adds link headers to optimise requests for assets, defaulting to preload behaviour
Expand All @@ -124,49 +123,6 @@ Adds link headers to optimise requests for assets, defaulting to preload behavio
- `priority` - a value of highest will add the link header _before_ all previously added resources that do not specify this (shodul not normally used by apps - used internally to ensure n-ui's resources are always loaded as wuickly as possible)
- `hashed` - if true the path to the asset will be resolved to the equivalent hashed aset path


# Anything below here isn't necessarily 100% up to date - n-ui has changed a lot recently and updating the docs is ongoing



### Dev workflow

[Overview of how the n-ui js bundle is delivered](https://docs.google.com/presentation/d/1UyeVsxE8GqGe-jVZDB5ppMLeRk2Ad49OuBBO8xDvyxs/edit#slide=id.p)

#### Standalone development

* `make build run` will
- start a server on `localhost:5005` which serves a demo page of most of the core n-ui components. *Note: Any changes to templates require restarting the server*
- build an n-ui bundle that will bootstrap the js and css for the page
* `make test-unit-dev` will run unit tests in Chrome using karma. To add tests for a new subcomponents, or to only run tests for a single subcomponent, modify the `componentsToTest` list in karma.config.js. In CI these tests are run in more browsers using saucelabs

#### Bower linking

To work with n-ui when it's bower linked into an app you will need to `export NEXT_APP_SHELL=local` in your app and then proceed exactly as you would for any other component

## A11y testing

We hope to be able to a11y test all components before they are used in an app and end up causing lots of applications to fail builds. For now we are testing components in CI using pa11y and this requires some additional set up when creating a new component. Any directory in the root is considered to be a component and will require this additional set up.

* Inside a component directory there must be a `pa11y-config.js` that must return JSON
* The must have an `entry` property and and may contain a `data` property if required
* The `entry` value should point to the main template for the component without any file extension and relative to the component root.
* The `data` can be used if you need to pass any fixture data to the component for testing.


## Adding subcomponents

**Don't** - n-ui is no longer a place to dump all next components. If you need to create a new shared component create a new repo for it and use the `n-ui-foundations` component to access primitive styles as used in next.

## JS usage

### Opting out of using a component provided by n-ui

If, for example, you want to use a beta of an origami component in a single app, or use React instead of preact
In your app’s webpack.config.js, you can pass an `nUiExcludes` array as an option to nWebpack e.g. `nUiExcludes: [‘React’, ‘React-Dom’]`



### Navigation
If you pass `withNavigation:true` in the init options, you will have navigation data available in `res.locals.navigation`. this data comes from polling the [navigation API](https://github.com/Financial-Times/next-navigation-api). This data is used to populate the various menus and navigation items on the apps. The following data is available

Expand Down
47 changes: 42 additions & 5 deletions browser/bundles/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
const nUi = require('../js/main');
// Expose entry points to shared bundle
import oAds from 'o-ads';
import ads from '../../components/n-ui/ads';
import tracking from '../../components/n-ui/tracking';
import oTracking from 'o-tracking';
import oDate from 'o-date';
import nUiFoundations from 'n-ui-foundations';
import oGrid from 'o-grid';
import oViewport from 'o-viewport';
import * as nImage from 'n-image';

nUi.bootstrap(window.ftNextUiConfig || {
preset: 'discrete',
preload: true
// Export some third party components we're unlikely to remove in a hurry
import ftdomdelegate from 'ftdomdelegate';
import superstore from 'superstore';
import superstoreSync from 'superstore-sync';

import { AppInitializer } from '../js/app-initializer';

// returns {flags, allStylesLoaded, appInfo}
const app = new AppInitializer();

window.FT.nUi = Object.assign({}, app.env, {
onAppInitialized: app.onAppInitialized,
ads: ads,
tracking: tracking,
_hiddenComponents: {
oAds,
oTracking,
oDate,
oViewport,
nUiFoundations,
oGrid,
nImage,
ftdomdelegate,
superstore,
superstoreSync
}
});

window.ftNextUi = nUi;
// must be after the definition of window.ft.Nui as some subcomponents will
// depend on it
// TODO - see if webpack 3's dynamic imports can replace the above hack
app.bootstrap(window.FT.nUiConfig || {
preset: 'discrete'
});
9 changes: 6 additions & 3 deletions browser/bundles/o-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ const appInfo = {
};

oErrors.init({
enabled: window.nextFlags.clientErrorReporting && appInfo.isProduction,
enabled: window.FT.flags.clientErrorReporting && appInfo.isProduction,
sentryEndpoint: 'https://62a990fd8dce4a27aafb006b58783f66@sentry.io/195030',
siteVersion: appInfo.version,
logLevel: window.nextFlags.clientDetailedErrorReporting ? 'contextonly' : 'off',
logLevel: window.FT.flags.clientDetailedErrorReporting ? 'contextonly' : 'off',
tags: {
appName: appInfo.name
},
filterError: function () {
return !window.FT.disableOErrors;
},
errorBuffer: window.errorBuffer || []
});

// turn on more detailed error reporting of ajax calls
if (window.nextFlags.clientAjaxErrorReporting) {
if (window.FT.flags.clientAjaxErrorReporting) {
const realFetch = window.fetch;
window.fetch = function (url, opts) {
return realFetch.call(this, url, opts)
Expand Down
151 changes: 151 additions & 0 deletions browser/js/app-initializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import ads from '../../components/n-ui/ads';
import tracking from '../../components/n-ui/tracking';
import date from 'o-date';
import header from '../../components/n-ui/header';
import oCookieMessage from 'o-cookie-message';
import footer from 'o-footer';
import { lazyLoad as lazyLoadImages } from 'n-image';
import * as serviceWorker from 'n-service-worker';
import DesktopAppBanner from 'n-desktop-app-banner';
import * as syndication from 'n-syndication';
import { perfMark } from 'n-ui-foundations';

export const presets = {
discrete: {
header: true,
footer: true,
date: true
},
complete: {
header: true,
footer: true,
date: true,
cookieMessage: true,
ads: true,
syndication: true
}
};

const waitForCondition = (conditionName, action) => {
window.FT.conditions[conditionName] ? action() : document.addEventListener(`FT.${conditionName}`, action);
};

// Dispatch a custom `ftNextLoaded` event after the app executes.
const dispatchLoadedEvent = () => {
let ftNextLoaded = false;
const ftNextLoadedTrigger = () => {
if (document.readyState === 'complete' && ftNextLoaded === false) {
ftNextLoaded = true;
window.dispatchEvent(new CustomEvent('ftNextLoaded'));
return true;
}
};
if (!ftNextLoadedTrigger()) {
window.addEventListener('load', ftNextLoadedTrigger);
document.onreadystatechange = ftNextLoadedTrigger;
}
};

export class AppInitializer {
constructor () {
this.onAppInitialized = this.onAppInitialized.bind(this);

const appInfo = {
isProduction: document.documentElement.hasAttribute('data-next-is-production'),
version: document.documentElement.getAttribute('data-next-version'),
name: document.documentElement.getAttribute('data-next-app'),
product: document.documentElement.getAttribute('data-next-product')
};

const flags = Object.assign(window.FT.flags, {
get: function (name) {
return this[name];
},
getAll: function () {
return this;
}
});

this.env = {
flags,
appInfo,
allStylesLoaded: new Promise(res => {
// if this element exists it means the page is setup to deliver critical/main css
if (document.querySelector('style.n-layout-head-css')) {
waitForCondition('allStylesLoaded', res);
} else {
res();
}
})
};
}

bootstrap (config) {
config = config || {};
this.enabledFeatures = Object.assign({}, presets[config.preset], config.features);
this.initializeComponents();
perfMark('nUiJsExecuted');
return this.env;
}

initializeComponents () {
const { flags, allStylesLoaded, appInfo } = this.env;

// FT and next tracking
tracking.init(flags, appInfo);

if (flags.get('serviceWorker')) {
serviceWorker
.register(flags)
.catch(() => { });

serviceWorker.message({ type: 'updateCache', data: {}});
} else {
serviceWorker.unregister();
}

if (this.enabledFeatures.header) {
header.init(flags);
}

if (this.enabledFeatures.date) {
date.init();
}

if (this.enabledFeatures.ads) {
ads.init(flags, appInfo, this.enabledFeatures.ads);
}

if (this.enabledFeatures.lazyLoadImages) {
lazyLoadImages();
}

// TODO - shouldn't it be possible to turn this off via the usual API?
if (flags.get('subscriberCohort') && flags.get('onboardingMessaging') === 'appPromotingBanner') {
new DesktopAppBanner();
}

allStylesLoaded
.then(() => {

if (this.enabledFeatures.footer) {
footer.init();
}

if (this.enabledFeatures.cookieMessage && flags.get('cookieMessage')) {
oCookieMessage.init();
}

if (this.enabledFeatures.syndication) {
syndication.init(flags);
}
});
}

onAppInitialized () {
perfMark('appJsExecuted');
dispatchLoadedEvent();
tracking.lazyInit(this.env.flags);
document.documentElement.classList.add('js-success');
}
}
Loading

0 comments on commit 1b86a6e

Please sign in to comment.