使用 React 和 Webpack 构建静态网站 已翻译 100%

oschina 投递于 2015/08/06 18:20 (共 12 段, 翻译完成于 08-26)
阅读 17438
收藏 92
5
加载中

I built a doubly static site using React (inc react-router) and Webpack. You can see the current demo here on GitHub  or continue reading the following post that explains the steps I took  during this experiment. This post proves the basic concept and there  will be a followup post covering the fine tuning needed to put this into  production.

Why

My blog currently uses Jekyll. Its a great way to build a static site  but for a while now I have been wanting to migrate off Jekyll onto  something more familiar. I don’t use Jekyll for anything other than my  blog so each time I go back to it there is a small learning curve. I  don’t feel the need to join the WordPress cult and Javascript is where  my heart is so some sort of custom node setup was the likely winner.

已有 1 人翻译此段
我来翻译

Options

Having narrowed down my search to Javascript there were still plenty of options available. I like the simple approach @code_barbarian took, rendering jade templates from a list of posts as mentioned here. Again though I don’t use jade that often. I have also looked at Harp several times but it never quite got me hooked. Remy Sharp has an interesting article on using Ghost or Harp. I also discovered morpheus  while I was working on this. It looks very interesting but I have ruled  it out as I don’t want to run anything on a server. And just this week I  read Presenting The Most Over Engineered Blog Ever which I’ll be keeping an interested eye on.

Obvious Choice

For me there was an obvious alternative. Recently I have really been  getting into React. I love it and have yet to encounter any major  hurdles. I am totally addicted to Hot Module Reloading as provided by  Webpack and Dan Abramovs React Hot Loader plugin. I am also really interested in exploring the idea of moving css into javascript (or JSS).

Eventually it clicked. React has the renderToStaticMarkup method. I could develop the entire site as a React app and render the result to static html.

已有 1 人翻译此段
我来翻译

Requirements

Time to set myself some requirements then.

  • Simple. One of the main goals is to replace Jekyll which is very  simple to use. I am prepared to play around a bit to get the initial  setup right as this is an experiment but ongoing use must be easy.

  • Flexible. The solution must not restrict what I can do with my blog (e.g. url formats, content).

  • A pleasurable build experience. Primarily my blog is written for me  by me. I should enjoy building it as much as using it. Development must  be simple but still have features that help (hot module reloading I’m  looking at you).

  • Doubly Static. The end result of the build step must be doubly  static meaning no further rendering on the server or the client. This is  a simple blog and I want static html files for each route that could be  served from anywhere.

Chosen Approach

  • A single page app built from React components

  • React Router to handle all possible routes for the site

  • Compiled by Webpack

  • All pages and posts listed in a javascript object along with their meta data

已有 1 人翻译此段
我来翻译

Getting Started

Time to start putting these ideas into practice and to start with I  just want to create and view a basic index page. For those playing along  at home here is the first commit.

elements/Layout.jsx is my starting point. Its a basic  React component that renders a full html page. There are some caveats to  rendering full pages in React but as long as its first rendered server  side its OK (see this discussion).

So next I need a script to render and serve the page. I’m using the  WebpackDevServer for this so I can take advantage of hot module  replacement. I create my webpack.config.js passing jsx files through the jsx-loader and react-hot-loader transforms and pointing webpack to dev/entry.jsx as an entry point for the bundle it will build. dev/entry.jsx simply renders a Layout component. server.js uses React.renderToString to write the result of creating a Layout component to file in dev/index.html and then starts the WebpackDevServer on localhost port 3000 to serve that file and handle live updates.

已有 1 人翻译此段
我来翻译

So now if run npm start the following will happen:

  • an elements/Layout.jsx component will be rendered to string and saved in dev/index.html

  • Webpack will create dev/bundle.js from the dev/entry.jsx starting point

  • Webpack will start an express server on port 3000

  • Webpack has setup hot module replacement so any changes I make are updated with out the page having to reload

Another Page

Thats a good development environment to start with but now its time to get this working for multiple pages.

At this point I’m going to setup some basic css styling. I’ll explain  what I’ve done but this approach will definitely be changed later on  before this is ready for production. First I update webpack.config.js to pass css files though the css-loader and style-loaders. I can then just require elements/style.css and /bower_components/pure/pure.css in entry.jsx to have them injected into the page by Webpack.

已有 1 人翻译此段
我来翻译

My strategy for creating the entire site from a single page will revolve around React Router. First I create elements/Routes.jsx as my main router. It uses elements/Layout.jsx as the handler at the top level and then has home and about routes pointing to new elements/Home.jsx and elements/About.jsx elements as handlers.  At this stage the new elements only render simple headings but are enough to see the router working.

elements/Layout.jsx gets updated now to render a new elements/LayoutNav.jsx  element so we can move between the home and about pages (with the help  of react-routers Link element). It also renders react-routers  RouteHandler element as its main content which will be either elements/Home.jsx or elements/About.jsx.

I also update dev/entry.jsx to run the elements/Routes.jsx router (passing in the current history location as the route) and rendering the handler return from it instead of rendering elements/Layout.jsx. Similarly I update server.js write the handler resulting from running the elements/Routes.jsx router with the current route set to ‘/‘ into dev/index.html.

So now running npm start will serve our multi route single page app.

Commit 2

已有 1 人翻译此段
我来翻译

A Little Fix

At this point the app works but only if you load localhost:3000.  Reloading the browser on localhost:3000/about will fail because Webpacks  express server doesn’t know about other routes. This is quickly fixed  with the following update to server.js

server.use('/', function(req, res) {   Router.run(Routes, req.path,  function (Handler) {     res.send(React.renderToString(React.createElement(Handler, null)));   }); });

Commit 3

Dynamic Page Titles

Its all well and good having two pages we can move between but the  html page title doesn’t update. I need a strategy to store data for each  page and  dynamically display it. The key to this is paths.js.  It contains an object with keys for each route and some helper methods  for extracting data from this object (at this point just titleForPath()). elements/Layout.jsx is now updated to lookup the title for the current route which it determines thanks to the react-router Router.State mixin.

Commit 4

已有 1 人翻译此段
我来翻译

Rendering Rethink

The elements/About.jsx and elements/Home.jsx  components are pretty rubbish and I am likely to already have existing  page contents as html that I would like to reuse without rewriting it as  a component. To do that I want to create Page.jsx that pulls content  from a html file listed in paths.js. Thats easy enough to do but how do I  load the content in a way that works in both node and the browser? I  can use Webpack loaders like raw-loader and html-loader but they present  a problem. They work fine within the Webpack context but they don’t  work outside of it. My current strategy for the intial server side  render would no longer work. Time for a rethink.

Dual Webpack Configs

The solution was actually quite simple and cleaned up server.js a lot. First I updated webpack.config.js to return an array of two configs. The first named browser is unchanged, so still points to the dev/entry.jsx entry point and compiles to dev/bundle.js. The second named server and targeting node points to a new entry point dev/page.jsx and compiles to dev/bundlePage.js. The function exported from dev/page.jsx returns the html for the path of a given request and is callable from node. server.js no longer needs any knowledge of my components (or React for that matter) and can use dev/page.jsx to get the html for any path it needs.

Commit 5

已有 1 人翻译此段
我来翻译

On The Same Page

Now thats sorted I can get back to making a reusable page element to pull in html content. I remove elements/Home.jsx and elements/About.jsx and replace them with elements/Page.jsx (also updating elements/Routes.jsx). This new component again makes use of react-routers Router.State mixin to pull the page title (heading) and html content via paths.js. The html content is inserted into the page using the dangerouslySetInnerHTML method. Note that the pageForPath() method of paths.js uses require.context('./pages', false, /^\.\/.*\.html$/); so that Webpack nows to transform html files in pages/ directory.

At this point I can easily add any additional pages I want without the need to create new components.

Commit 6

Whats A Blog Without Posts

Pages are done so now its time to add posts and a blog index page to list all posts.

The first thing I did was to create elements/PathsMixin.js to make it easier to access data from paths.js and remove the need to repeat logic. It depends on getCurrentPathname and getCurrentParams from Router.State  so it sets them as required in the contextTypes property (meaning React  will throw a warning if you try to use the PathsMinixin without  Router.State). This cool because things like var title = paths.titleForPath(this.getPathname()); can now become var title = this.getPathMeta('title’); within components using this mixin. Mixins for the win.

已有 1 人翻译此段
我来翻译

paths.js was updated to list the posts in a similar way  to how it lists pages but with posts having some extra data such as md,  published and preview. It also gains a postforPath method that utilises a new require context to load and transform markdown files is the posts/ directory.

elements/Post.jsx is very similar to elements/Page.jsx  but displays the transformed markdown for a post as well as the date it  was published. The display of the date was a great opportunity to  create a reusable component elements/Moment.jsx to format and render the date. This is where components really shine.

elements/Blog.jsx is a custom component that grabs data via the elements/PathsMixin.js  and loops of each post to create a list. Nothing too exciting (and  please ignore how I have done the styling) but it shows just how quickly  a new feature can be added. elements/Routes.jsx gets updated for the blog and post routes as does elements/LayoutNav.jsx.

Commit 7

已有 1 人翻译此段
我来翻译
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

Hancoson
Hancoson
mark
crossmix
crossmix
ok
巫云
巫云
可以用来作为静态化方案?
一别经年17
一别经年17
挺好的!
wfifi
wfifi
类似hexo
齐齐打瓜距地
齐齐打瓜距地
额. 翻译辛苦了!
qijingq
qijingq
Webpack 编译一次好慢。
_Elvis
_Elvis
不错的尝试
返回顶部
顶部