Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scully): add Flash Prevention Plugin #418

Merged
merged 3 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(scully): add Flash Prevention Plugin
Initial add of flash prevention plugin
  • Loading branch information
aaronfrost committed Mar 23, 2020
commit 58aec565257ce69f046becbcc41ce9cbcadfcfe3
1 change: 1 addition & 0 deletions docs/recommended-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ _If you would like to add a plugin to this list, please submit a PR to the `docs

- `minifyHtml` render plugin - [https://www.npmjs.com/package/scully-minify-html](https://www.npmjs.com/package/scully-minify-html)
- `disableAngular` render plugin - [https://www.npmjs.com/package/scully-disable-angular](https://www.npmjs.com/package/scully-disable-angular)
- `flashPrevention` render plugin - [https://www.npmjs.com/package/scully-plugin-flash-prevention](https://www.npmjs.com/package/scully-plugin-flash-prevention)

[Full Documentation ➡️](scully.md)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"scully:publish:major": "tsc -p ./tsconfig.scully.json && npm version major",
"scully:test:ssl:self": "npm run generate -- serve --ssl",
"scully:test:ssl:file": "npm run generate -- serve --ssl --ssl-cert=./certs/localhost.cert --ssl-key=./certs/localhost.key",
"flash:build": "rm -rf ./dist/scully-plugin-flash-prevention && cp -r projects/scully-plugin-flash-prevention dist/scully-plugin-flash-prevention",
"flash:build": "rm -rf dist/scully-plugin-flash-prevention && tsc -p ./projects/scully-plugin-flash-prevention/tsconfig.lib.json && node ./projects/scully-plugin-flash-prevention/build.js",
"changelog": "npx conventional-changelog -p scully -i ./CHANGELOG.md -s && git add ./CHANGELOG.md",
"commit": "git add . && npx git-cz",
"commit:select": "npx git-cz",
Expand Down
61 changes: 61 additions & 0 deletions projects/scully-plugin-flash-prevention/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,64 @@ app-root-scully {
```

That's all it takes to get set up.

## Options

This plugin has a series of options you can pass it when you call `getFlashPreventionPlugin(<OPTIONS HERE>)`.
Here is a description of those options.

- [appRootSelector](#approotselector)
- [appLoadedClass](#apploadedclass)
- [mockAttributesBlacklist](#mockattributesblacklist)
- [appRootAttributesBlacklist](#approotattributesblacklist)

### appRootSelector

If you `AppComponent` has a selector that isn't `app-root`, you can use this option to pass your app's custom
`app-root` selector. If your `AppComponent` use the selector `scully-app`, you would do pass that as an arg to
the `getFlashPreventionPlugin` function:

```javascript
getFlashPreventionPlugin({appRootSelector: 'scully-app'});
```

### appLoadedClass

Once you app loads, this plugin will add the class `loaded` to the body of your app. If you need to use a different
class (besides the default `loaded`) you can pass that class name in here. If you want to use the class `fploaded`
do the following:

```javascript
getFlashPreventionPlugin({appLoadedClass: 'fploaded'});
```

### mockAttributesBlacklist

This plugin makes a copy of your `app-root` and calls it `app-root-scully`. It leaves all of the original
attributes from `app-root` on the copy. If your `app-root` has an attribute `foo` with the value of `"bar"`
then your mock app root (`app-root-scully`) will also get that attribute.

If you want to make sure that the mock element doesn't get certain attributes, you can use this blacklist
to remove certain attributes from the mock element. Example: if the original `app-root` had `foo="bar"` as an
attribute, and you want to exclude that on the mock copy of it, you would do the following:

```javascript
getFlashPreventionPlugin({mockAttributesBlacklist: ['foo']});
```

This would remove the `foo` attribute from `app-root-scully`. This looks to see if the attr `startsWith` the
blacklisted item you passed. This means if you pass `foo`, and the attribute is `foobar="baz"` it will remove
the `foobar` attribute as it starts with `foo`.

### appRootAttributesBlacklist

This plugin can remove attributes from the pre-rendered version of your `app-root`. If your `app-root` had
the attribute `foo="bar"` you can use this blacklist to remove attributes from the source `app-root`.

```javascript
getFlashPreventionPlugin({appRootAttributesBlacklist: ['foo']});
```

This would remove the `foo` attribute from `app-root`. This looks to see if the attr `startsWith` the
blacklisted item you passed. This means if you pass `foo`, and the attribute is `foobar="baz"` it will remove
the `foobar` attribute as it starts with `foo`.
22 changes: 22 additions & 0 deletions projects/scully-plugin-flash-prevention/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const fx = require('fs-extra');
const {join} = require('path');

fx.copyFileSync(
join(__dirname, 'package.json'),
join(__dirname, '..', '..', 'dist', 'scully-plugin-flash-prevention', 'package.json')
);
fx.copyFileSync(
join(__dirname, 'README.md'),
join(__dirname, '..', '..', 'dist', 'scully-plugin-flash-prevention', 'README.md')
);

const fppSrc = fx.readFileSync(
join(__dirname, '..', '..', 'dist', 'scully-plugin-flash-prevention', 'src', 'flash-prevention.plugin.js')
);
const modSrc = fppSrc.toString('UTF-8').replace('../../../dist/scully', '@scullyio/scully');

fx.writeFileSync(
join(__dirname, '..', '..', 'dist', 'scully-plugin-flash-prevention', 'src', 'flash-prevention.plugin.js'),
modSrc,
'UTF-8'
);
1 change: 1 addition & 0 deletions projects/scully-plugin-flash-prevention/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {getFlashPreventionPlugin} from './src/flash-prevention.plugin';
9 changes: 3 additions & 6 deletions projects/scully-plugin-flash-prevention/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
{
"name": "scully-plugin-flash-prevention",
"version": "0.0.32",
"version": "0.0.37",
"peerDependencies": {
"@angular/common": "^9.0.0-rc.9",
"@angular/core": "^9.0.0-rc.9",
"@scullyio/scully": "^0.0.78",
"tslib": "^1.10.0"
"@scullyio/scully": "^0.0.78"
},
"main": "src/lib/public-apis.js",
"main": "index.js",
"contributors": [
"Aaron Frost <[email protected]>"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
const {registerPlugin} = require('../../../../dist/scully');
const {appendToHead} = require('./utils');
import {registerPlugin} from '../../../dist/scully';
import {appendToHead} from './utils';

let AppRootSelector = 'app-root';
let LoadedClass = 'loaded';
const DiscountFlashPrevention = 'discountFlashPrevention';
const validator = async () => {};
registerPlugin('render', DiscountFlashPrevention, discountFlashPreventionPlugin, validator);
registerPlugin('router', DiscountFlashPrevention, async ur => [{route: ur}], validator);
const FlashPrevention = 'ScullyPluginFlashPrevention';
const AppRootAttrsBlacklist = ['_nghost', 'ng-version'];
const MockRootAttrsBlacklist = [];

module.exports = {
getDiscountFlashPreventionPlugin,
};
registerPlugin('render', FlashPrevention, flashPreventionPlugin);
registerPlugin('router', FlashPrevention, async ur => [{route: ur}]);

function getDiscountFlashPreventionPlugin({appRootSelector, appLoadedClass} = {}) {
interface FlashPreventionPluginOptions {
appRootSelector?: string;
appLoadedClass?: string;
appRootAttributesBlacklist?: string[];
mockAttributesBlacklist?: string[];
}

export function getFlashPreventionPlugin({
appRootSelector,
appLoadedClass,
appRootAttributesBlacklist,
mockAttributesBlacklist,
}: FlashPreventionPluginOptions = {}) {
if (appRootSelector) {
AppRootSelector = appRootSelector;
}
if (appLoadedClass) {
LoadedClass = appLoadedClass;
}

return DiscountFlashPrevention;
pushItemsToArray(appRootAttributesBlacklist, AppRootAttrsBlacklist);
pushItemsToArray(mockAttributesBlacklist, MockRootAttrsBlacklist);

return FlashPrevention;
}

async function discountFlashPreventionPlugin(html, handledRoute) {
async function flashPreventionPlugin(html, handledRoute) {
let newHtml = await createSecondAppRoot(html);
newHtml = await addBitsToHead(newHtml);
return newHtml;
Expand All @@ -35,33 +48,18 @@ async function createSecondAppRoot(html) {
const appRootEndRegExp = new RegExp(`\<\/${appRootSelector}\>`, 'g');
let [openTagMatch] = html.match(appRootStartRegExp);
const [closeTagMatch] = html.match(appRootEndRegExp);
const blackListAttrs = ['_nghost', 'ng-version'];
let cleanedOpenTag = openTagMatch
.replace('>', ' >')
.split(' ')
.reduce((acc, attr) => {
// Does the html attr exist in the blacklisted attrs
const attrIsBlacklisted = blackListAttrs.reduce((acc, cur) => {
if (acc === true) return acc;

return attr.startsWith(cur);
}, false);
let cleanedAppRootOpenTag: string = fetchCleanedOpenTag(openTagMatch, AppRootAttrsBlacklist);
let cleanedMockOpenTag: string = fetchCleanedOpenTag(openTagMatch, MockRootAttrsBlacklist).replace(
appRootSelector,
`${appRootSelector}-scully`
);

if (!attrIsBlacklisted) {
acc.push(attr);
}
return acc;
}, [])
.join(' ')
.replace(' >', '>');
let newHtml = html
// replace the closing tag with replacement scully closing tag
.replace(closeTagMatch, `${closeTagMatch.replace(appRootSelector, `${appRootSelector}-scully`)}`)
// replace opening tag with cleaned app root tag AND replacement scully app root tag
.replace(
openTagMatch,
`${cleanedOpenTag}${closeTagMatch}${openTagMatch.replace(appRootSelector, `${appRootSelector}-scully`)}`
);
.replace(openTagMatch, `${cleanedAppRootOpenTag}${closeTagMatch}${cleanedMockOpenTag}`);
``;
return newHtml;
}
Expand All @@ -87,3 +85,33 @@ async function addBitsToHead(html) {

return appendToHead(html, contentScript);
}

function pushItemsToArray(src, dest) {
if (src) {
if (src.length && !Array.isArray(src)) {
src = [src];
}
src.forEach(item => dest.push(item));
}
}

function fetchCleanedOpenTag(str: string, arr: string[]): string {
return str
.replace('>', ' >')
.split(' ')
.reduce((acc, attr) => {
// Does the html attr exist in the blacklisted attrs
const attrIsBlacklisted = arr.reduce((acc, cur) => {
if (acc === true) return acc;

return attr.startsWith(cur);
}, false);

if (!attrIsBlacklisted) {
acc.push(attr);
}
return acc;
}, [])
.join(' ')
.replace(' >', '>');
}
7 changes: 0 additions & 7 deletions projects/scully-plugin-flash-prevention/src/public-api.js

This file was deleted.

32 changes: 32 additions & 0 deletions projects/scully-plugin-flash-prevention/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./projects/scully-plugin-flash-prevention",
"declaration": true,
"downlevelIteration": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"importHelpers": true,
"emitDeclarationOnly": false,
"lib": ["es2018", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/scully-plugin-flash-prevention",
"sourceMap": true,
"target": "es2018",
"types": ["node"],
"typeRoots": ["../../node_modules/@types"],
"allowJs": true,
"paths": {
"@scullyio/scully": ["../../dist/scully"],
"*": ["node_modules/*"]
},
"plugins": [
{
"disabledRules": ["check-return-value", "must-use-promises"]
}
]
},
"files": ["./index.ts"],
"exclude": ["bin/**/*", "bin/**/*.d.ts", "bin/index.js", "../dist/**/*"]
}
6 changes: 2 additions & 4 deletions scully.sampleBlog.config.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const {
getDiscountFlashPreventionPlugin,
} = require('./projects/scully-plugin-flash-prevention/src/public-api');
const {getFlashPreventionPlugin} = require('./projects/scully-plugin-flash-prevention');

/** load the plugin */
require('./extraPlugin/extra-plugin.js');
require('./extraPlugin/tocPlugin');
require('./extraPlugin/voidPlugin');

const FlashPrevention = getDiscountFlashPreventionPlugin();
const FlashPrevention = getFlashPreventionPlugin();

exports.config = {
/** outDir is where the static distribution files end up */
Expand Down