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

Framework: Implement AsyncLoad for code-splitting using Babel plugin #8544

Merged
merged 7 commits into from
Nov 2, 2016

Conversation

aduth
Copy link
Contributor

@aduth aduth commented Oct 6, 2016

Supersedes #5356

This pull request is related to #5356, implementing <AsyncLoad />, but leveraging a new Babel plugin transform-wpcalypso-async to handle transformations of prop values under the hood to require.ensure or require depending on the current environment configuration.

The new transform plugin also exposes a lower-level asyncRequire function for general usage. See plugin documentation for more information.

Testing Instructions:

Navigate to the editor, verifying that the sharing accordion is displayed after visiting the page.

  1. Navigate to the post editor
  2. Select a site, if prompted
  3. Note that a Sharing accordion appears in the sidebar

Follow-up Tasks:

We should improve <AsyncLoad /> to accept additional props to control when the module is required, so that we can load in response to specific interactions (e.g. hovering the accordion title).

cc @mtias @ehg @mcsf @seear @umurkontaci

@aduth aduth added Framework [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. Components labels Oct 6, 2016
@matticbot
Copy link
Contributor

@@ -112,7 +112,14 @@ if ( CALYPSO_ENV === 'desktop' || CALYPSO_ENV === 'desktop-mac-app-store' ) {
jsLoader = {
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: [ 'babel-loader?cacheDirectory' ]
loader: 'babel',
query: {
Copy link
Contributor

Choose a reason for hiding this comment

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

probably want to turn cacheDirectory back on?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably want to turn cacheDirectory back on?

Yep, good catch. Fixed in baab477a9e1a6a1e9c5ef2872ea897dd63c297d2

@aduth
Copy link
Contributor Author

aduth commented Oct 6, 2016

A visualization of the transformations applied by the plugin:

With code splitting:

Code Splitting

Without code splitting:

No code splitting

loader: 'babel',
query: {
plugins: [
[ 'transform-wpcalypso-async', {
Copy link
Member

Choose a reason for hiding this comment

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

Do we want this enabled for the server bundle?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we want this enabled for the server bundle?

I think so? Otherwise AsyncLoad and asyncRequire won't be transformed.

Copy link
Member

Choose a reason for hiding this comment

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

We don't want multiple bundles being generated though, in any case, so can we set async to false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't want multiple bundles being generated though, in any case, so can we set async to false?

Yeah, that makes sense.

@aduth aduth force-pushed the try/async-load-babel-plugin branch from baab477 to 053fb7a Compare October 7, 2016 14:33
@@ -12,7 +12,7 @@ import Accordion from 'components/accordion';
import AccordionSection from 'components/accordion/section';
import Gridicon from 'components/gridicon';
import CategoriesTagsAccordion from 'post-editor/editor-categories-tags/accordion';
import EditorSharingAccordion from 'post-editor/editor-sharing/accordion';
import EditorSharingAccordion from 'async-component!post-editor/editor-sharing/accordion';
Copy link
Contributor

Choose a reason for hiding this comment

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

how does this work with tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how does this work with tests?

Probably not very well, and I'm sure same goes for base <AsyncLoad />. Will need to think about this some more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe the loader could check for the NODE_ENV and return the original source if it's testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I guess the issue is that Webpack isn't used at all in the context of tests, so it'll choke on the loader syntax...

Copy link
Member

Choose a reason for hiding this comment

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

I would probably split the async-component! loader syntax up into another PR, since IMHO it adds a fair bit of complexity.

Copy link
Contributor Author

@aduth aduth Oct 21, 2016

Choose a reason for hiding this comment

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

I would probably split the async-component! loader syntax up into another PR, since IMHO it adds a fair bit of complexity.

What would you suggest be included in this pull request after that would be removed?

Copy link
Member

Choose a reason for hiding this comment

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

Anything needed for asyncRequire and AsyncLoad. That way, I think we can split up changes to the build pipeline into more discrete pieces.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anything needed for asyncRequire and AsyncLoad. That way, I think we can split up changes to the build pipeline into more discrete pieces.

So, to be clear, for this instance we'd use <AsyncLoad /> directly instead of the loader in this pull request, and defer the loader to a future one?

Just want to be sure we're including something in this one. 😆

Copy link
Member

Choose a reason for hiding this comment

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

So, to be clear, for this instance we'd use directly instead of the loader in this pull request, and defer the loader to a future one?

Sorry, yes. I see this PR as being for usage of AsyncLoad and asyncRequire?, along with the babel plugin required to transform those. Then the import webpack loader being in another PR, so we're not changing too much at once :)

@aduth
Copy link
Contributor Author

aduth commented Oct 7, 2016

Couple issues to resolve:

  • Currently with the Webpack loader it's emitting a chunk with the full absolute path of the module
  • I think I'll need to update the Babel plugin to pass the async chunk name with path separators replaced

@aduth
Copy link
Contributor Author

aduth commented Oct 10, 2016

  • I found that you can reference Babel plugins via local file paths, so I decided to bring the plugin inside the project (at least for faster iterations in this branch).
  • My hacky workaround for the Mocha tests issue is to enhance the Babel plugin to strip the loader syntax from the import path (e279107), at which point Mocha will treat it as a synchronous require. I think a long-term solution would be to run modules through Webpack before passing them to Mocha, which would be more aligned with our generated bundles.

Copy link
Member

@ehg ehg left a comment

Choose a reason for hiding this comment

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

Oops, forgot to submit.

@@ -156,6 +164,6 @@ if ( config.isEnabled( 'webpack/persistent-caching' ) ) {
webpackConfig.plugins.unshift( new HardSourceWebpackPlugin( { cacheDirectory: path.join( __dirname, '.webpack-cache', 'client' ) } ) );
}

webpackConfig.module.loaders = [ jsLoader ].concat( webpackConfig.module.loaders );
webpackConfig.module.postLoaders = [ jsLoader ];
Copy link
Member

Choose a reason for hiding this comment

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

Having the babel transformation happen in a postLoader worries me a little. Was this just for the import syntax change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having the babel transformation happen in a postLoader worries me a little. Was this just for the import syntax change?

Yes, if I recall correctly, it was so that:

(a) The async component loader could return ES6
(b) The Babel plugin would be run last to strip the async-component! import prefix

@@ -12,7 +12,7 @@ import Accordion from 'components/accordion';
import AccordionSection from 'components/accordion/section';
import Gridicon from 'components/gridicon';
import CategoriesTagsAccordion from 'post-editor/editor-categories-tags/accordion';
import EditorSharingAccordion from 'post-editor/editor-sharing/accordion';
import EditorSharingAccordion from 'async-component!post-editor/editor-sharing/accordion';
Copy link
Member

Choose a reason for hiding this comment

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

I would probably split the async-component! loader syntax up into another PR, since IMHO it adds a fair bit of complexity.

@aduth aduth force-pushed the try/async-load-babel-plugin branch from e279107 to c6757d9 Compare October 26, 2016 16:38
@aduth
Copy link
Contributor Author

aduth commented Oct 26, 2016

Ok, I performed a surgical rebase, squashed a few commits, but preserved the Webpack loader and a revert so it's kept in history for future reference. How does this look now @ehg?

};
}

componentWillMount() {
Copy link
Contributor

Choose a reason for hiding this comment

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

this may also need to have componentWillReceiveProps in case we get into a situation where React decides to reuse an instance instead of spinning up a new one. Given how this will be used, it's probably unlikely, but worth doing.

Copy link
Contributor Author

@aduth aduth Oct 26, 2016

Choose a reason for hiding this comment

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

It also raises the point that the Babel plugin transforms the string into a function which is created anew for every render. I might experiment a bit to see how difficult it would be to pull the function to the top of the transformed module so that we can do a strict equality check in componentWillReceiveProps before calling the require prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This brings to mind another Babel plugin that might be interesting to experiment with:

http://babeljs.io/docs/plugins/transform-react-constant-elements/

Copy link
Contributor

@blowery blowery Oct 26, 2016

Choose a reason for hiding this comment

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

that thing would be amazing for gridicons

er, no, they take a dynamic size. darn.

const isAsync = state.opts.async;

// In both asynchronous and synchronous case, we'll finish by
// calling reqquire on the loaded module
Copy link
Contributor

Choose a reason for hiding this comment

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

speling on require

Copy link
Contributor Author

Choose a reason for hiding this comment

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

speling on require

Fixed in rebased 5b9a658

@ehg
Copy link
Member

ehg commented Oct 27, 2016

Ok, I performed a surgical rebase, squashed a few commits, but preserved the Webpack loader and a revert so it's kept in history for future reference. How does this look now @ehg?

It looks good. Thanks for this, I hope not too much anesthesia was required :)

@blowery
Copy link
Contributor

blowery commented Oct 27, 2016

Bit late but something to maybe consider?

https://github.com/airbnb/babel-plugin-dynamic-import-webpack

@aduth
Copy link
Contributor Author

aduth commented Oct 27, 2016

Bit late but something to maybe consider?

It's not obvious the point of this plugin vs. simply using require.ensure... to turn it into a Promise I guess? In our case we needed to handle transforming to either require.ensure or require based on the environment before allowing it to be processed by Webpack.

@blowery
Copy link
Contributor

blowery commented Oct 28, 2016

@aduth yeah, true. I'm happy with what we have anyway. :)

@aduth aduth force-pushed the try/async-load-babel-plugin branch from c6757d9 to c26d2c9 Compare October 28, 2016 18:38
@aduth
Copy link
Contributor Author

aduth commented Oct 28, 2016

Introduced function hoisting for the transformed require prop in c26d2c9 for use by componentDidUpdate lifecycle method of <AsyncLoad /> component in 7640e48.

@aduth
Copy link
Contributor Author

aduth commented Oct 28, 2016

Updated in/out with { "async": true }:

image

@ehg
Copy link
Member

ehg commented Nov 1, 2016

The hoisting changes look good. I think we can merge this now! Great work @aduth.

@ehg ehg added [Status] Ready to Merge and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Nov 1, 2016
@mtias
Copy link
Member

mtias commented Nov 1, 2016

Thanks for working on this!

@aduth aduth force-pushed the try/async-load-babel-plugin branch from 7640e48 to 808cbb6 Compare November 2, 2016 17:45
@aduth
Copy link
Contributor Author

aduth commented Nov 2, 2016

Did a few more final rounds of testing (production mode, IE, desktop app). Looking good on all fronts. Going to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants