Meteor + Webpack: ES6 modules, hot-code-patching, fixes load order & more


Oh sorry I didn't get a chance to finish the guide... I started to add a wiki. :frowning: FlowRouter wants to boot up right away so we have to tell it to wait:
// somewhere in meteor_core/client/lib/foo.js

FlowRouter.wait();

then in your webpack code at the end of your routes call:

// somewhere in /app/routes.js or wherever

FlowRouter.initialize();

Using ReactLayout is prob. the best bet. I’m using a little helper function to clean up the routes:

FlowRouter.route('/posts/:postId', {
  action: (params) => renderPage(PostsPage, MainLayout, params),
});

btw reading through the 'meteor specific issues' – issue might help in other areas (like testing and REPL) https://github.com/jedwards1211/meteor-webpack-react/issues/8

And i’ll try and update the wiki this weekend with some tips and tricks:
https://github.com/jedwards1211/meteor-webpack-react/wiki

1 Like

Things are moving fast… https://github.com/gaearon/react-transform-boilerplate is the new React Hot Loader. Well, we knew the React Hot Loader was a bit of a hack, or anyway, Dan expressed his thoughts about this.

I haven’t dived into it yet. It’s worth jumping to this one I guess to stay on top of things.

1 Like

I actually tried it out with meteor wheni saw webpack gaining popularity. But was left disappointed ,lots of errors !

But after seeing your post, I think I’ll try it again.
But is there any pitfalls?

1 Like

Did this project give you error before or just webpack in general? There were a few commits that broke for some people without global npm packages but that should be fixed now.

But is there any pitfalls?

Mainly you can’t easily use the meteor shell because it won’t let you require (they remove it somehow). This means you have to import a module and then expose a global to access it for debugging.

Testing integration stuff also requires a global inside of Velocity. Again, exposing a global for testing may be needed. A lot of the integration stuff is already exposed though (Meteor.call, etc…) I’m using Karma for unit testing and that’s working very well with import and require (hoping to PR a basic preset config soon).

You can’t hot-load a file that contains a Meteor method or collection. This isn’t a deal breaker, you just have to manually hit refresh after.

See this for a running list: https://github.com/jedwards1211/meteor-webpack-react/issues/8


[quote="rolf, post:22, topic:9110, full:true"] Things are moving fast.. https://github.com/gaearon/react-transform-boilerplate is the new React Hot Loader. Well, we knew the React Hot Loader was a bit of a hack, or anyway, Dan expressed his thoughts about this.

I haven’t dived into it yet. It’s worth jumping to this one I guess to stay on top of things.
[/quote]

Nice! I didn’t know this was finished! Thanks!!

BTW, for those who have’t seen it yet, here’s a nice gif of how hot-patching works as opposed to a hot full-reload:

@rolf just sent a PR to swap out the older react-hot-loader with the new react-transform plus error handling. Thanks again for the heads up!

https://github.com/jedwards1211/meteor-webpack-react/pull/36

Just pushed a Blaze example app with FlowRouter to my fork:

@SkinnyGeek1010 I think it’s worth it to update to React 0.14-rc1 while you are writing docs anyway, no? So far (and especially for simple demos) it’s a simple replacement but with the advantages of a split React and ReactDOM and no need to do findDOMNode when this.refs.x already references the node.

Since the whole stack is mostly bleeding edge, I think it’s fine to use this instead of 0.13.

It could be interesting to compare this with meteor-webpack-react-kickstart and find the best way. I’m not too familiar with this one so let me know if I’m wrong.

meteor-webpack-react-kickstart

  • Everything is served with the Meteor server (thanks to webpack:hot-load)
  • When an error occured, you have a nice red box that describe the error
  • Server-rendering is disabled while developing to allow hot reload
  • Server-rendering is working with no additional work in production (if you want it) with react-router-ssr
  • Any code Meteor needs (like collections, methods and subscriptions) have to be in the meteor folder
    (it has to be because your Webpack bundle is not saved on the disk but in memory for hot reload)

meteor-webpack-react

  • You have to link your page with the webpack-dev-server
  • When an error occur, the old version is still running
  • Your collections can be with your React.js components
  • Haven’t seen it work with server-rendering but I guess it would be easy to plug react-router-ssr

Any though on how we can all do things better? Any though on how Meteor could handle this better?

1 Like

Hi @benoitt ! I just saw your new project last night but haven’t tried it out yet. It looks very interesting.

Any though on how we can all do things better? Any though on how Meteor could handle this better?

I think we could both learn from both projects :smiley: One thing I like about yours is how simple the configs are. Avital mentioned in a Meteor issue that MDG is watching the project to see how things pan out… I hope they will integrate webpack into core and allow custom configs! I think raising interest in webpack will help do so.


I think there are two major differences and the rest are just preferences. At the end of the day both projects take ES6 code, create a bundle, and drop it into the Meteor app. Meteor then creates it’s own production build when deploying or running in prod.

Here’s how meteor-webpack-react does it:

Build your app the JavaScript way

The largest one is that meteor-webpack-react takes a stance of allowing you to build your meteor app the way every standard javascript app is made. Use NPM modules to build an app and create a bundle when done.

It doesn’t try to be transparent and do all things the ‘meteor way’. This can come off as a hack (which I guess it is lol) but I like to challenge dogma and find the ‘best’ way to build my apps.

Doing it this way also makes Meteor feel like a set of modules that you can import to help get realtime into your app (well except they’re global modules lol).

If you need access to a collection in your app (say to add a transform) you don’t have to go out into a new directory and find the collections in your meteor app (although you can do this… I have a legacy app that has some code in meteor_core). To me it’s easier to think about it like this, though it’s purely personal preference IMHO:

//   /collections/posts.js
export const Posts = new Mongo.Collection('posts');

// somewhere else
import Posts from 'collections/posts';
console.log(  Posts.findOne()  );

How Meteor core and packages are used

Since core and packages are loaded before the webpack bundle, you can just use the global namespace to use Meteor or packages. This keeps things cohesive because you can use them where you want and you don’t have to switch between “two apps” if you need to modify a subscription. This may seem awkward at first but once you’re used to it, it’s quite nice to cd app open your editor and then pretty much ignore all the outer folders.

Easier code sharing with client/server

By putting all code into the app/ folder it makes it very easy to share things… just import it from main.client.js and it’s on the client, import it from main.server.js and it’s on the server.

No built in SSR

There isn’t any SSR support because we don’t assume that you’ll use router X. Both FlowRouter and React router are fairly simple to configure for SSR (though I think an SSR branch would be nice!)

More complex

If you compare webpack configs the meteor-webpack-react is a lot more complicated. But that’s the nice things about the boilerplate… no-one should have to write this for each new app :smile:

It has configs for both client and server bundles, and uses webpack dev server which complicates things more because it sets up a server to stream changes over socket.io (which is why it inserts JS into the head to connect).


Also some things to clear up (I admit the docs are not super clear):

Everything is served with the Meteor server (thanks to webpack:hot-load)

That’s also how meteor-webpack-react works, it essentially drops two bundles (client/server) into the meteor folder and then meteor itself serves the files. You can actually just ./prod then run cd meteor_core && meteor deploy foo.meteor.com and it will deploy like a normal meteor app.

When an error occured, you have a nice red box that describe the error

This was recently added as well. It’s pretty nice!

3 Likes

This might be do-able if @jedwards wanted to do so. It’s super easy to do so:

change react version to ‘0.14.0-rc1’ in packages.json, npm install --save react-dom@0.14.0-rc1

require react-dom and change the render line to:

ReactDom.render(<App/>, document.getElementById('root'));

and the Blaze Template helper, change to ReactDom:

this.view = Blaze.render(this.props.template, ReactDom.findDOMNode(this.refs.root));
> this.view = Blaze.render(this.props.template, ReactDom.findDOMNode(this.refs.root));

even simpler with just this.refs.root as it’s already the DOM node in 0.14 by default…

this.view = Blaze.render(this.props.template, this.refs.root);
1 Like

Hey so if i use a webpack version do i have to worry about it breaking in the future with new meteor versions? do i just simple cd into meteor folder and do meteor update?

Hot reload is 1000x better than the current meteor reload, and load order issues is definitely attractive for a serious web app.

1 Like

Great question. For 99% of it there won’t be any issue. Conceptually it’s just dropping in your compiled bundle into the meteor_core app (for dev it’s a bit more complicated).

However, there is a polyfill for babel and ES5 that could clash with Meteor… in fact early on there was a clash with the Number constructer because MDG monkeypatched it and when the polyfill modified it there was an issue with check. This is why it now builds a custom polyfill of core-js when it’s first initialized (also gives you the latest automatically).

There could potentially be issues with double loading libraries as well. For example if you require moment with NPM (as you should… wrapper packages are ridiculous), someone could have a package that requires moment as well. The solution is to fork the package and use a weak dependency (which arguably it should have anyway).

To be honest 80% of the Meteor specific packages i’ve used i’ve had major problems with. I’ve stopped using any packages unless it’s heavily used and tested (like collection-hooks for ex.).

That being said i’m using it in production daily with 3 apps and Andy & Luigi are too.

Most of the fixes that are added now are for a better developer experience and better maintainability.


I've started on a Wiki to help explain all this and more. I'll be filling it out in time.
3 Likes

Did you get around that big speed test perhaps? I’m curious how fast this thing rebuilds, since 1.2 Meteor will stay at around 20 seconds per rebuild in my case (100 files maybe).

Plus, the more I’m thinking about it, the more my preference goes towards NPM and away from Meteor packages. So that’s a nice bonus :smile:

Cheers.

1 Like

Close. I’m working on porting it over from Alt to Redux which should be done today and then over the weekend it might get strated. The main issue is converting all the tests over too :disappointed:

With some recent changes to config I’ve dropped it down to sub 1second changes using eval for ugly source maps… It will def be shorter.

I have ported another app that went from ~8 seconds to 1.2 ish… With around 70-80 ish files. Not sure if I mentioned that in this thread lol. The npm modules and imports alone are bough for me to switch :+1:

Here’s an update with the new React-Router 1.0 RC… it’s really slick for using webpack code splitting. They offer a declarative API to do this very nicely. The routes end up being in a nested folder structure.

Guide

https://github.com/rackt/react-router/blob/master/docs/advanced/DynamicRouting.md

Example App (non meteor app)

https://github.com/rackt/react-router/tree/master/examples/huge-apps

Example:

const CourseRoute = {
  path: 'course/:courseId',

  getChildRoutes(location, callback) {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades'),
      ])
    })
  },

  getComponents(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Course'))
    })
  }
}
2 Likes

@SkinnyGeek1010, this looks really awesome, but how does this actually work? Different JS is downloaded on different routes without re-downloading what you already have?

Edit: Right now I’m trying to figure out how get the following all mixed together for the best possible setup and I’m not even sure if this is the ideal:

  • Webpack integration
  • React Router with dynamic loading of components
  • Redux
  • Efficient components that don’t re-render unnecessarily (is there something out there for this?)

The only problem I have with Redux is how do you make it play with Meteor’s reactive data? Most of the examples I’ve seen are putting subscription calls randomly out in the open and it’s not clear what is the best way to get Meteor data flowing down from the store, and how you put them in different reducers without them all re-running at the same time.

Also, if we adopt this method, does this pretty much say good bye to ReactiveVar/Dict and Session for UI state management?

1 Like

So the best way to explain how it works is to watch Pete Hunt talk about it here: https://www.youtube.com/watch?v=VkTCL6Nqm6Y and then the following docs should clear up the rest: http://webpack.github.io/docs/code-splitting.html I haven’t tried this yet but want to soon with React-Router.


So there’s two ways to use Redux… store all state in Redux including minimongo collections or you can use Redux just for local state (not persisted in the DB). To answer your last question the latter would essentially replace Reactive Var/Dicts (assuming you’re using React, if not Redux can use a Reactive Dict instead). If you do this then you’d need to use the mixin to ‘sideload’ the data from the meteor mixin.

However I find it easier to conceptually think about data flowing in the app if it’s stored in one single object tree and going through one path.

The only con with this approach is that you will use twice as much memory per collection because it’s stored in mini-mongo and Redux.

Basically you setup a tracker autorun and whenever the collection changes any data… dispatch a changed event to Redux and a collections reducer can do a Posts.find() which is the newest representation of data, and then just return it in the reducer (we don’t need to merge changes because minimongo handles that).

I let the topmost view handle the subscriptions. Ideally these are ‘view controllers’ and only handle data fetching. it only renders one child and passes data down to it via props. You want as many component to use props as possible.

Redux is completely decoupled from subscriptions then… it only listens for an event type and merges it if it matches. Nothing more.

Inserts and updates happen in the action creator because they have side effects (the reducer has to be pure for time travel). The new data from the insert/update will be sent in via the collection tracker above.

That essentially closes the loop.

new post

  • The user clicks an add button
  • The view gathers data from the form and passes it into dispatch( createPost(payload) )
  • Action creator invokes Posts.insert(someData) and could return something but not in this case
  • … If this has an error a new action is dispatched indicating it failed else
  • Collection changes (due to insert above) so tracker callback fires {type: POSTS_CHANGED, payload: [...]}
  • Action (object payload itself not func) gets passed down through every reducer, which ever one matches, it will handle updating state as normal. In this case the collections reducer will return a new array for updated posts
  • Redux updates state and if using React, any views listening to collections.posts will get new data as this.props.posts
  • React view can hook into shouldComponentUpdate and do a === because it’s immutable so a change creates a whole new object
  • Re-render

Redux and the React-Redux will handle making sure everything is happening as efficient as possible. If 3 changes happen at the same time the UI won’t change until the next cycle of the event loop (I think).

####Here’s a very simple example of Redux (and devtools!) using a collection and local state:

https://github.com/AdamBrodzinski/meteor-flux-leaderboard/tree/redux


I don’t have a webpack example with redux but it’s fairly simple to use the repo in the top of this thread with the leaderboard as a boilerplate (note the leaderboard needs a conditional to not load devtools in production).

Also if you read the Redux guide but did’t code along, it’s hard to grok. Using it makes it much more clear.

2 Likes

Seems like the only missing piece from your stack is facebook’s Relay. May be off topic, but could you expand why it makes sense to keep using meteor for data instead of going the declarative approach & moving to graphQL?

I just tried the lazily loading components via React Router routes and it works! This is going to be huge! Not only does it lazily load components but it lazily evaluates the route definitions as well. This is going make apps so much smaller on initial load.

I took the example that Rackt has here and stuck it into @jedwards’s setup and it worked great. The only thing I haven’t figured out is how to use the non-hashbang routing and use history (Edit: I figured it out now).