构建基于组件的网站






4.55/5 (6投票s)
无状态函数式组件让构建多页面网站变得轻而易举。
引言
SPA(单页应用)一直是构建Web应用的现代、响应式和结构化的方式,基于组件的Web开发提高了开发人员的生产力和开发效率。随着Web平台的最新进展,特别是HTML5/Ajax/WebSocket/HTTP2,以及流行的JavaScript框架,Web应用开发得到了很好的发展。然而,构建一个多页面网站却并非如此。
经典网站仍然由多个页面组成,主要包含静态内容。加载速度、易于导航、页面级SEO、用户体验跟踪等是多页面网站的基本特性。对于一个规模化的内容导向型网站,一致的外观和感觉、页面间可共享的组件以及快速更新和部署对于Web运营也至关重要。使用SPA技术构建静态内容网站存在一些重大挑战和缺点。
随着Web应用程序复杂性的增加,还需要将面向公众的网站与需要身份验证的功能分开,SPA方法仅适用于大型网站的某个部分。特别是当涉及多个团队、优先级、架构和技术栈时,将SPA分解为多页面网站可以使不同的部分并行且独立地进行架构、开发和更新。
本文介绍了一种基于组件的技术,用于构建具有高可组合性、可重用性、可维护性和效率的静态内容导向型多页面网站。
项目源代码和快速入门指南可在https://github.com/modesty/react2html找到。
背景
React2Html是一个开源项目,旨在解决SPA在构建基于静态内容的多页面网站时面临的挑战。它利用React.js的无状态函数式组件模型,通过可组合性、可重用性和可共享性来简化开发。它倡导“组件”思维——页面中的所有内容都可以由底层可共享组件构成,鼓励“关注点分离”,通过独特的“构建时组件模型”实现站点范围的快速更新,该模型易于组合、定制和重用。
React2Html的核心是一个“构建”脚本,用于将React组件和页面编译成HTML标记,连接、压缩和打包客户端JavaScript(如果有),连接、压缩和打包SASS CSS模块,还会为每个页面创建基于页面名称的目录,当然还会将资产(包括图像、图标、字体和其他媒体文件)复制到目标目录的正确位置。
使用React2Html,源代码树以JavaScript和JSX中的组件以及SASS中的CSS模块开始,每个页面都由指定目录中的这些组件构成。编译(运行构建脚本)后,所有源代码将被编译并输出为每个页面HTML文件在自己的目录中,引用打包的客户端JavaScript和CSS,以及复制过来的资产文件。
例如,一个多页面网站的典型源代码树结构可能如下所示

构建脚本将在编译后生成“target”目录,使其可部署

以下是一些值得注意的方面
- src/scripts/client下的所有JavaScript都将成为捆绑JavaScript的一部分,每个客户端JavaScript文件都开发有自己的ES6/ES2015模块,由babel编译,并由webpack压缩和捆绑
- src根目录和- src/images中的所有文件将直接复制到- target根目录和- target/images目录
- src/scripts/pages下的所有JavaScript文件将编译为- target/[page_name]/index.html。例如,- src/scripts/pages/about编译为- target/about/index.html。这种目录结构使得页面导航的URL清晰简洁
- src/scripts/model下的所有JavaScript文件都只在构建时用于模型,它使得在构建时不同页面可以共享- src/scripts/component
使用代码
首先,克隆GitHub仓库
$ cd [dev_root]
$ git clone https://github.com/modesty/react2html.git
$ cd react2html
安装依赖项
$ npm i
运行构建
$ npm run build
现在您可以检查target目录中的输出。
对于开发和调试,只需运行
$ npm start
它将自动清理target目录,重新编译整个源树,自动在https://:8181启动默认浏览器,并开始监视源文件更改。每当源代码或资产文件有保存的更改时,它将根据需要重新编译,然后自动刷新浏览器。
构建脚本解释
所有构建脚本都位于src/tool目录中,它负责编译、打包和复制的繁重工作。它提供了所有必需的功能,无需更改即可开箱即用。如果您需要进一步定制或扩展它,以下是一些关键的定制点

- base.config- 自定义源目录和目标目录名称、脚本和SASS目录名称以及入口点。
- 自定义资产文件类型
- 在运行时从CDN加载的CSS和JavaScript库列表
 
- base.helper- 自定义localhost开发服务器地址、端口、根服务目录、默认文件等。
 
- build.asset- 如果您不想要Apache .htaccess文件,请在此处将其删除
 
- 如果您不想要Apache 
- build.watcher- 自定义要监视哪些文件和目录以进行自动重新编译和自动刷新
 
- webpack.config- 自定义webpack模块(uglify、SASS等)、源映射、输出目标等。
 
添加新页面
现在我们可以看到向示例网站添加新页面是多么容易和快速。假设源代码结构和构建脚本保持不变,我们想添加一个名为“contact”的新页面,可以通过https://:8181访问。
首先,在src/scripts/pages目录下创建一个名为contact.js的新文件。新页面可以通过组合组件轻松构建
"use strict";
import React from 'react';
import Conf from '../../../tool/base.config';
import Head from '../components/Head';
import Header from '../components/Header';
import Main from '../components/Main';
import Footer from '../components/Footer';let pkg = require("../../../package.json");
const Contact = () => {
   let pageTitle = `联系我们`;
   return (
      <html className="no-js" lang="en">
      <Head title={pageTitle} description={pkg.description} styles={Conf.styles} scripts={Conf.scripts} rel="../"/>      <body className='container-fluid'>
      <Header title={pageTitle} rel="../"/>
      <Main>
         <h1>React2Html</h1>
         <p>给作者发邮件: <a href="mailto:modestyz@hotmail.com?subject=About%20CoeProject%20Article">modestyz@hotmail.com</a>
         </p>
      </Main>
      <Footer rel="../"/>
      </body>
      </html>
   );
};export default Contact;它组合了用于页面元标签的Head组件,引用了来自CDN的库js/css以及来自target/styles路径的捆绑js/css,它还利用Header、Main、Footer组件,通过少量代码保持网站的通用和一致结构以及外观,这样页面代码可以更专注于其自身特定内容。
其次,构建它
$ npm start
就是这样,新页面位于https://:8181/contact
编译后的输出位于target/contact/index.html,所有内容都经过压缩和最小化,以提高加载性能。
当页面标题、页脚、样式或CDN链接更改时,联系页面本身无需更改,编译将自动从相应的组件中获取更改。
更新模型
要在作为Header组件一部分的navbar中添加指向我们新contact页面的链接,只需更新其在src/scripts/model/menu.js中的模型即可。
const MenuModel = [
   {name: 'Home', href: "."},
   {name: 'Portfolio', href: "#", children: [
      {name: "Products", href: "#"},
      {name: "Services", href: "#"}
   ]},
   {name: 'Team', href: "#", children: [
      {name: "Creative Team", href: "#"},
      {name: "Technical Team", href: "#"},
      {name: "Subject Domain Expert", href: "#"}
   ]},
   {name: 'Testimonial', href: "#", children: [
      {name: "Partners", href: "#"},
      {name: "Customers", href: "#"}
   ]},
   {name: 'Support', href: "#", children: [
      {name: "Contact Us", href: "#"},
      {name: "Schedule a Visit", href: "#"}
   ]},
   {name: 'About Us', href: "./about/"},
   {name: 'Contact Us', href: "./contact/"}
];
 同样,更新页脚模型src/scripts/model/footer.js,在Footer组件中添加指向contact页面的链接。
const FooterModel = {
   siteLinks: [
      {name: "About", href: "./about"},
      {name: "Legal", href: "#"},
      {name: "Privacy", href: "#"},
      {name: "Contact", href: "./contact"}
   ],
   socials:[
      {id: "icon-facebook", href: "http://facebook.com"},
      {id: "icon-twitter", href: "http://twitter.com"},
      {id: "icon-youtube", href: "http://youtube.com"},
      {id: "icon-linkedin", href: "http://linkedin.com"},
      {id: "icon-dribbble", href: "http://dribbble.com"},
      {id: "icon-rss", href: "http://rss.com"}
   ],
   copyRight: "Modesty Zhang © 2015 - 2016",
   addressLine1: "7330 Clairemont Mesa Blvd,",
   addressLine2: "San Diego, CA 92111",
   phone: "+1-111-222-3333",
   email: "modestyz@hotmail.com"
};
无状态函数式组件
React.js在v0.14中引入了无状态函数式组件,它也是react2html使用组件创建多页面网站的核心。无状态和函数式意味着react2html中的所有组件都没有内部状态,如果传入的属性相同,则输出的标记也将相同。
在reac2html的案例中,所有组件的属性都通过model传入。例如,给定上面的HeaderModel,以下是Header组件的代码
"use strict";import React from 'react';import Menu from './Menu';import processRelPath from '../model/header';const Header = ({ title, rel }) => {
   let dataModel = processRelPath(rel);
   let {homeLink, bannerLink} = dataModel;   return (
      <header className='header'>
         <div className="row">
            <div className="col-md-3 col-xs-12 hidden-xs">
               <div className="logo">
                  <a href={homeLink.href} title={homeLink.name}>
                     <img src={homeLink.img} alt={title} />
                  </a>
               </div>
            </div>
            <div className="col-md-6 col-xs-12">
               <div className="banner-area">
                  <a href={bannerLink.href}>
                     <img src={bannerLink.img} alt={bannerLink.name} className="img-responsive" />
                  </a>
               </div>
            </div>
            <div className="col-md-3 hidden-sm hidden-xs">
               <div className="soc-area">
                  <div className="icons-social">
                     {dataModel.socials.map( s => <a href={s.href} className={s.id} key={s.id}></a>)}
                  </div>
               </div>
            </div>
         </div>
         <Menu rel={rel}></Menu>
      </header>
   );
};export default Header;Footer组件的代码非常相似,使用简单的函数语法,没有内部状态,在构建时表现得像一个纯组件或哑组件。无状态函数式组件模式鼓励通过这些简单组件的不同级别组合来构建网页,使开发人员能够使用JavaScript和JSX表达数据驱动的用户界面。从抽象意义上讲,它利用React的思想来实现创建与React无关的组件及其组合,从而创建更复杂的多页面网站。
根据网站的性质和复杂性,model可以在构建时或运行时通过API / 数据库 / CMS获取和构建。React组件和其他JavaScript库和框架也可以在运行时用于响应数据存储更改。示例代码只关注带有静态数据模型的静态网站。
尽管组件代码和编译输出与React无关,但当前实现确实利用了React的“同构”特性,ReactDOMServer.renderToStaticMarkup是使react2html在构建时高效工作的关键支持。
总结
如果您正在构建一个需要频繁更新的静态内容多页面网站,并面临SEO、初始加载时间、浏览器历史和导航、干净URL、分析和跟踪、代码分区和重复等挑战,那么react2html可以提供帮助。它在构建时利用无状态函数式组件,通过可重用性和可共享性实现数据驱动的多页面网站开发。
更多详细信息和所有源代码可在https://github.com/modesty/react2html找到。欢迎贡献和拉取请求。

