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

Implement code splitting with react-loadable #84

Merged
merged 12 commits into from
Apr 14, 2017
Merged

Conversation

richardscarrott
Copy link
Member

@richardscarrott richardscarrott commented Apr 4, 2017

Approach:

  • Disable code splitting (via config) server side to avoid needing to load in chunks.
  • Split Webpack bootstrap into own chunk to allow split chunks to load prior to main in client. (also has the added benefit of not invalidating the revisioned main chunk when a split chunk changes hash)
  • Flush deterministic module ids (similar to react-helmet's rewind) from wrapped react-loadable instead of absolute module paths as Webpack makes it hard to talk in absolute paths (and of course, both client and server renderers are bundled with webpack).

TODO:
See review comments.

  • Style error and loader components (will do in another PR)
  • Use functions expressions (with arrows) over function declarations and update prettier to use 2 spaces. (prob worth doing in another PR after this is merged)
  • Re-generate webpack config snapshots and review.

@@ -1,7 +1,7 @@
import React, { PropTypes } from 'react';
import serialize from 'serialize-javascript';

function Html({ css, js, html, head, initialState }) {
function Html({ css, mainJs, chunkJs, html, head, initialState }) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge mainJs and chunkJs as single js prop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and include bootstrap)

// (without fetchData...) if it's moved up then we don't split any redux selectors / actions.
// - Perhaps it's worth digging up that redux-saga SSR depenency solution instead so we rely
// less on components.
// - Or perhaps I can wrap think middleware to provide an api to identifiy what data is needed?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Delete comment

// dispatch `fetchQuoteIfNeeded` when the LoadingComponent mounts to start fetching
// data deps earlier, or maybe even introduce another static method e.g. 'preFetch'
// which could just make the api call and have Index dispatch it... Worth benchmarking.
export default liftFetchData(webpackRequireWeakId)(IndexLoadable);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const enhance = compose(liftFetchData(webpackRequireWeakId), loadable);
export default enhance({
    loader: () => import('./Index'),
    LoadingComponent,
    webpackRequireWeakId
});

@@ -0,0 +1,14 @@
/**
* Lifts static `fetchData` method to `Component` to allow server to fetch
* data without having to import Index.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*Lifts static fetchData method from weakId component to Component, deliberately circumventing static require / import to prevent Webpack from bundling the weakId component and it's deps.

return class extends Component {
constructor() {
super();
moduleIds.push(options.webpackRequireWeakId());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this is used twice, perhaps moduleIds should be a unique Set?

try {
serverStats = require(SERVER_STATS_PATH);
} catch (ex) {
throw new Error('Client bundle server-stats.json not found. Try running `npm run build`');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixup error message.


// NOTE: We're making a trade off for more aggresive code splitting (i.e. includes
// action creators) for waterfall requests when fetching the chunk and the data
// in the client.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try splitting reducer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// FETCH_QUOTE_REQUEST,
// FETCH_QUOTE_SUCCESS,
// FETCH_QUOTE_FAILURE
// } from 'actions/quote/quote';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split reducers

return {
...state,
isFetching: false,
value: action.payload
};
case FETCH_QUOTE_FAILURE:
case 'FETCH_QUOTE_FAILURE':
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use strings

src/server.js Outdated
function getJs(stats, moduleIds) {
return [
getJsByChunkName('bootstrap', stats),
...getCodeSplitChunks(stats, moduleIds),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to getCodeSplitJs

{}
)
: {},
// Not sure there's much point in moving node_modules to externals but
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort out why removing externals causes:

WARNING  Compiled with 1 warnings                                                                                                                          10:57:15 PM

 warning  in ./~/encoding/lib/iconv-loader.js

9:12-34 Critical dependency: the request of a dependency is an expression

/**
* Move webpack bootstrap to separate chunk to allow code split chunks to be
* loaded *before* main.
*/
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this comment is necessary...it's already documented in createConfig/index.js

})
}),
// Deterministic module ids for long term caching
new webpack.HashedModuleIdsPlugin()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds ~10kb to total bundle size vs default indexed module ids but prob fine until I can look into webpack records.

@@ -2,10 +2,10 @@

const StatsWebpackPlugin = require('stats-webpack-plugin');

module.exports = ({ stats }) => {
module.exports = ({ stats, name }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make ternary like bootstrapChunk

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.473% when pulling 8c10be6 on code-splitting into 1ffe020 on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.1%) to 98.507% when pulling 995e1f5 on code-splitting into 1ffe020 on master.

@richardscarrott richardscarrott merged commit 8958b05 into master Apr 14, 2017
@richardscarrott richardscarrott deleted the code-splitting branch April 14, 2017 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants