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

Dynamic Routes #2

Open
iansinnott opened this issue Jan 28, 2016 · 9 comments
Open

Dynamic Routes #2

iansinnott opened this issue Jan 28, 2016 · 9 comments
Assignees

Comments

@iansinnott
Copy link
Owner

AKA routes that look like

<Route path='posts/:id' component={Post} />

The issue

We need dynamic routes to support creating an entire category of sties including blogs. However currently we don't have a way to turn something like posts/:id into a file directly. Normally we would have something like this:

posts/
  hellow-world.md
  second-post.md
  still-here.md

Which would generate the following HTML...

posts/
  hello-world.html
  second-post.html
  still-here.html

But in our case React Router has no way of knowing the contents of our posts/ directory so how would it know what files to generate?

Most static site generators would read the posts/ directory and then generate static sites from those files. However, that requires the user to either learn how to configure their generator or learn the conventions for creating dynamic content.

We could do the same thing:

fs.readdirSync('./posts').forEach(filename => {
  // 1. Read contents of file
  // 2. Generate a static HTML file for it
});

But then the user would have to know how to hook into this process. It is a first-class goal of this project to not force the user to learn some new technology. Just add this plugin to a webpack config and go. This is the issue I'd like to solve.

@iansinnott
Copy link
Owner Author

Update

Thinking out loud here. One possible approach is webpack contexts. For example, given a posts/ directory containing js files that export components to be rendered as blog posts we could do this:

// Import all necessary modules up here...

// Get a list of post components using webpack context
const context = require.context('./posts', false, /\.js$/);
const posts = context.keys().sort().map(context);

export const routes = (
  <Route path='/' title='App' component={App}>
    {/* Other router config here... */}
    {posts.map((module, i) => {
      const Component = module.__esModule ? module.default : module;
      return <Route key={i} path={String(i)} component={Component} />;
    })}
    <Route path='*' title='404: Post Not Found' component={NotFound} />
  </Route>
);

export default routes;

@iansinnott iansinnott self-assigned this Mar 11, 2016
@iansinnott
Copy link
Owner Author

Another potential solution would be to expect directories to exist based on routes. For example, given the following router config:

// Import all necessary modules up here...

export const routes = (
  <Route path='/' title='App' component={App}>
    <Route path='posts' component={Posts}>
      <IndexRoute component={PostList} />
      <Route path=':id' component={Post} />
    </Route>
    <Route path='*' title='404: Post Not Found' component={NotFound} />
  </Route>
);

export default routes;

We would see the :id route and infer the that the user must have a directory on the filesystem containing the files necessary to generate these routes. In this case, we would look up the tree and see the parent route has a path of 'posts' so we would expect a posts/ directory at the project root.

Drawbacks:

  • What would the Post component look like? How would the data necessary to build the component be passed in?
  • Convention. Not necessarily bad, but again it's a first-class priority of this project not to require the user to learn a whole new system in order to take advantage of building static sites. The user would have to know to include that directory. We would throw meaningful errors if not found of course.
  • How could we customize output filename, i.e. the output file path? First step would just be to use the filename to generate the output, but it would be best to let the user configure it with a function.

@scherler
Copy link

scherler commented May 23, 2016

Why not allow to pass path ([]) as option and then in https://github.com/iansinnott/react-static-webpack-plugin/blob/master/src/index.js#L83 merge the user one and the ones from the router.

That would remove any dependencies on file system or such and open it for other underlying logic to generate the paths.

---webpack.config.js
/*
* the variable could be generated from outside and then set dynamically.
* in my use case I fetch some json data (jenkins-plugins) and if that is fetched I want to invoke a static export of all the detail page of each plugin. So I would end up with an array of 1200+ and pass it to the plugin
*/
const paths = ['/', '/post/xxx', '...']; 

new StaticSitePlugin({
      paths: paths, 
      src: 'app',
      stylesheet: '/app.css',
      favicon: '/favicon.ico',
    }),

--index.js#L83
    const routerPaths = getAllPaths(Component);
    log('Parsed routes:', routerPaths);
    const { paths: userPaths } = this.options;
    const paths = Array.from(new Set(routerPaths.concat(userPaths)));

Or are there some pitfalls that I do not see?

@iansinnott
Copy link
Owner Author

iansinnott commented May 23, 2016

Hm, where would you get the paths from?

I'm not entirely clear how this would work, but you might be on to something here. So let's assume you already had the array of paths, how would use use them in your routes.js file?

If you put together or a complete example of how the API might look I'm happy to check it out. I would also welcome a pull request if you think you have a solution :)

@scherler
Copy link

@iansinnott the path you can inject or write them as I did in the above example. I will have a look now since I am may need to change how the plugin resolves the paths.

As short explanation I am ATM working on https://github.com/scherler/react-static-boilerplate/tree/redux the redux branch to add it to the boilerplate if you like it. However there is a problem in the way we normally would invoke the store vs. the export all routes.

If you look into the above branch you will find

    <Provider store={store}>
      <Router routes={routes} history={browserHistory} />
    </Provider>

To inject the store you need to to this in the top parent or you will need to do it in each route. I am still playing around to see whether I can move it around somehow, will let you know.

@f0rr0
Copy link

f0rr0 commented Aug 21, 2016

@iansinnott
Gatsby does this by traversing the file-tree.
https://github.com/gatsbyjs/gatsby/blob/master/lib/isomorphic/create-routes.js

It uses https://github.com/markdalgleish/static-site-generator-webpack-plugin/ underneath which provides a routes option. It does not use react-router, so all the routes need to be specified by the user. If I am not wrong, @scherler was suggesting something along that line (merging user provided routes into the already existing ones from react-router).

However, I like your require.context approach. Any progress on that?

@iansinnott
Copy link
Owner Author

Hey @sidjain26 thanks for the ping. I haven't looked at this issue in a while, because for whatever reason I haven't yet run into the need for dynamic routes. I've been using this plugin extensively but it's all been for sites without dynamic routing.

In my own minimal testing in the past the context approach actually worked quite well. it's currently the option I favor because it is explicit and doesn't require the user to know any special configuration—this is a first class goal of this project.

What's also nice is that the require.context approach should work out of the box right now, because it doesn't require any specific configuration within this plugin. It's simply leveraging a feature of webpack. So although I don't currently have a guide for how to do this it should be a viable option for anyone wanting to implement dynamic routes.

Once I rewrite my blog using this plugin I will definitely figure out how to do dynamic routing.

@lifeiscontent
Copy link
Contributor

@iansinnott how would you do something like this for I18n routes? e.g. /about and /fr/about?

@iansinnott
Copy link
Owner Author

@lifeiscontent I'm not sure, but my guess is that Webpack has a i18n solution. Have you tried internationalizing a web app with webpack yet?

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

No branches or pull requests

4 participants