Skip to content

Commit

Permalink
<AfterScripts />, <AfterStyles /> (#279)
Browse files Browse the repository at this point in the history
* change react version

* add AfterStyles and AfterStyles

* format code

* fix a security vulnerability
  • Loading branch information
nimaa77 authored Jan 18, 2020
1 parent 7a9eb5b commit 287c7c8
Show file tree
Hide file tree
Showing 10 changed files with 744 additions and 505 deletions.
45 changes: 20 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ try {
}
...
```

Don't forget to pass your custom params to `<After/>` in `client.js`:

```js
// ./src/client.js
...
Expand Down Expand Up @@ -403,16 +405,21 @@ After.js works similarly to Next.js with respect to overriding HTML document str
```js
// ./src/Document.js
import React from 'react';
import { AfterRoot, AfterData } from '@jaredpalmer/after';
import {
AfterRoot,
AfterData,
AfterScripts,
AfterStyles,
} from '@jaredpalmer/after';

class Document extends React.Component {
static async getInitialProps({ assets, data, renderPage }) {
static async getInitialProps({ renderPage }) {
const page = await renderPage();
return { assets, data, ...page };
return { ...page };
}

render() {
const { helmet, assets, data } = this.props;
const { helmet } = this.props;
// get attributes from React Helmet
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
Expand All @@ -427,19 +434,12 @@ class Document extends React.Component {
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
{assets.client.css && (
<link rel="stylesheet" href={assets.client.css} />
)}
<AfterStyles />
</head>
<body {...bodyAttrs}>
<AfterRoot />
<AfterData data={data} />
<script
type="text/javascript"
src={assets.client.js}
defer
crossOrigin="anonymous"
/>
<AfterData />
<AfterScripts />
</body>
</html>
);
Expand All @@ -455,20 +455,20 @@ If you were using something like `styled-components`, and you need to wrap you e
// ./src/Document.js
import React from 'react';
import { ServerStyleSheet } from 'styled-components';
import { AfterRoot, AfterData } from '@jaredpalmer/after';
import { AfterRoot, AfterData, AfterScripts } from '@jaredpalmer/after';

export default class Document extends React.Component {
static async getInitialProps({ assets, data, renderPage }) {
static async getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet();
const page = await renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
);
const styleTags = sheet.getStyleElement();
return { assets, data, ...page, styleTags };
return { ...page, styleTags };
}

render() {
const { helmet, assets, data, styleTags } = this.props;
const { helmet, styleTags } = this.props;
// get attributes from React Helmet
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
Expand All @@ -488,13 +488,8 @@ export default class Document extends React.Component {
</head>
<body {...bodyAttrs}>
<AfterRoot />
<AfterData data={data} />
<script
type="text/javascript"
src={assets.client.js}
defer
crossOrigin="anonymous"
/>
<AfterData />
<AfterScripts />
</body>
</html>
);
Expand Down
2 changes: 1 addition & 1 deletion examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"express": "^4.16.2",
"raf": "^3.4.0",
"razzle": "^3.0.0",
"react": "^16.2.0",
"react": "^16.3.2",
"react-dom": "^16.3.3",
"react-helmet": "^5.2.0",
"react-router-dom": "^4.2.2"
Expand Down
12 changes: 6 additions & 6 deletions examples/basic/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6862,7 +6862,7 @@ prop-types@^15.5.4:
loose-envify "^1.3.1"
object-assign "^4.1.1"

prop-types@^15.6.0:
prop-types@^15.6.0, prop-types@^15.6.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
Expand Down Expand Up @@ -7176,14 +7176,14 @@ react-side-effect@^1.1.0:
exenv "^1.2.1"
shallowequal "^1.0.1"

react@^16.2.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.0.tgz#fc5a01c68f91e9b38e92cf83f7b795ebdca8ddff"
react@^16.3.2:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83"
integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
prop-types "^15.6.2"

read-pkg-up@^1.0.1:
version "1.0.1"
Expand Down
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@
"scripts": {
"prepare": "tsc -p tsconfig.build.json",
"start": "tsc -p tsconfig.build.json -w",
"format": "prettier --trailing-comma es5 --single-quote --write 'packages/*/*.js' 'packages/*/!(node_modules)/**/*.js'",
"precommit": "lint-staged",
"format": "prettier --trailing-comma es5 --single-quote --write \"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|mdx)\"",
"test": "razzle test --env=jsdom --no-cache",
"test:ci": "CI=true yarn test"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|mdx)": [
"prettier --trailing-comma es5 --single-quote --write",
Expand All @@ -51,7 +55,7 @@
},
"peerDependencies": {
"express": ">=4",
"react": ">=16",
"react": ">=16.3.2",
"react-helmet": ">=5.2.0",
"react-router-dom": ">=4.2.2",
"serialize-javascript": ">=1.5.0"
Expand All @@ -67,9 +71,10 @@
"@types/react-router-dom": "^4.3.4",
"@types/serialize-javascript": "^1.3.2",
"express": "^4.16.2",
"husky": "^0.14.3",
"husky": ">=1",
"jest": "^23.6.0",
"lint-staged": "^6.1.0",
"lint-staged": ">=8",
"prettier": "^1.19.1",
"razzle": "^2.4.0",
"react": "^16.5.0",
"react-dom": "^16.5.0",
Expand Down
32 changes: 25 additions & 7 deletions src/After.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,18 @@ class Afterparty extends React.Component<AfterpartyProps, AfterpartyState> {
// save the location and data so we can render the old screen
// first we try to use previousLocation and then location from props
this.setState(prevState => ({
previousLocation: prevState.previousLocation || this.props.location
previousLocation: prevState.previousLocation || this.props.location,
}));

const { data, match, routes, history, location, staticContext, ...rest } = nextProps;
const {
data,
match,
routes,
history,
location,
staticContext,
...rest
} = nextProps;

loadInitialProps(this.props.routes, nextProps.location.pathname, {
location: nextProps.location,
Expand All @@ -64,7 +72,7 @@ class Afterparty extends React.Component<AfterpartyProps, AfterpartyState> {
// if data is not for current location just don't do anything
if (this.props.location !== location) {
// should we save this data in prefetcherCache ?
return
return;
}

// Only for page changes, prevent scroll up for anchor links
Expand Down Expand Up @@ -100,18 +108,28 @@ class Afterparty extends React.Component<AfterpartyProps, AfterpartyState> {
render() {
const { previousLocation, data } = this.state;
const { location } = this.props;
const initialData = this.prefetcherCache[(previousLocation || location).pathname] || data;
const initialData =
this.prefetcherCache[(previousLocation || location).pathname] || data;

return (
<Switch location={previousLocation || location}>
{initialData && initialData.statusCode && initialData.statusCode === 404 && <Route component={this.NotfoundComponent} path={location.pathname} />}
{initialData && initialData.redirectTo && initialData.redirectTo && <Redirect to={initialData.redirectTo} />}
{initialData &&
initialData.statusCode &&
initialData.statusCode === 404 && (
<Route
component={this.NotfoundComponent}
path={location.pathname}
/>
)}
{initialData && initialData.redirectTo && initialData.redirectTo && (
<Redirect to={initialData.redirectTo} />
)}
{getAllRoutes(this.props.routes).map((r, i) => (
<Route
key={`route--${i}`}
path={r.path}
exact={r.exact}
render={(props) =>
render={props =>
React.createElement(r.component, {
...initialData,
history: props.history,
Expand Down
85 changes: 59 additions & 26 deletions src/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import * as React from 'react';
import serialize from 'serialize-javascript';
import { DocumentProps } from './types';

export const __AfterContext = React.createContext(
{} as DocumentProps & { html: string }
);

export class Document extends React.Component<DocumentProps> {
static async getInitialProps({ assets, data, renderPage }: DocumentProps) {
static async getInitialProps({ renderPage }: DocumentProps) {
const page = await renderPage();

return { assets, data, ...page };
return { ...page };
}

render() {
const { helmet, assets, data } = this.props;
const { helmet } = this.props;
// get attributes from React Helmet
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
Expand All @@ -25,37 +28,67 @@ export class Document extends React.Component<DocumentProps> {
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
{assets.client.css && (
<link rel="stylesheet" href={assets.client.css} />
)}
<AfterStyles />
</head>
<body {...bodyAttrs}>
<AfterRoot />
<AfterData data={data} />
<script
type="text/javascript"
src={assets.client.js}
defer
crossOrigin="anonymous"
/>
<AfterData />
<AfterScripts />
</body>
</html>
);
}
}

export function AfterRoot() {
return <div id="root">DO_NOT_DELETE_THIS_YOU_WILL_BREAK_YOUR_APP</div>;
}
export const AfterRoot = () => {
return (
<__AfterContext.Consumer>
{({ html }) => (
<div id="root" dangerouslySetInnerHTML={{ __html: html }} />
)}
</__AfterContext.Consumer>
);
};

export function AfterData({ data }: any) {
export const AfterData: React.FC<{ data?: any }> = ({ data }) => {
return (
<script
id="server-app-state"
type="application/json"
dangerouslySetInnerHTML={{
__html: serialize({ ...data }),
}}
/>
<__AfterContext.Consumer>
{({ data: contextData }) => (
<script
id="server-app-state"
type="application/json"
dangerouslySetInnerHTML={{
__html: serialize({ ...(data || contextData) }),
}}
/>
)}
</__AfterContext.Consumer>
);
}
};

export const AfterStyles = () => {
return (
<__AfterContext.Consumer>
{({ assets }) =>
assets.client.css && <link rel="stylesheet" href={assets.client.css} />
}
</__AfterContext.Consumer>
);
};

export const AfterScripts = () => {
return (
<__AfterContext.Consumer>
{({ assets }) =>
assets.client.js && (
<script
type="text/javascript"
src={assets.client.js}
defer
crossOrigin="anonymous"
/>
)
}
</__AfterContext.Consumer>
);
};
20 changes: 10 additions & 10 deletions src/NotFoundComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ class NotFound extends React.Component {
// just for test purpose
static data = `The Page You Were Looking For Was Not Found`;

render() {
return (
<Route
render={({ staticContext }) => {
if (staticContext) staticContext.statusCode = 404;
return NotFound.data;
}}
/>
)
}
render() {
return (
<Route
render={({ staticContext }) => {
if (staticContext) staticContext.statusCode = 404;
return NotFound.data;
}}
/>
);
}
}

export default NotFound;
Loading

0 comments on commit 287c7c8

Please sign in to comment.