@ artsy/ stitch (英语)
帮助您的组件和模板依赖和平共处
概览
在Artsy,我们有一些已经生产了一段时间的Node.js应用. 强制例如"CoffeeScript"和"Jade"在"CoffeeScript"让许多意义大了;"CoffeeScript"解决了JavaScript Pre-ES6的许多问题,"Jade"让与"HTML"的合作更加优雅了. 随着时间的推移和新技术的出现,这些解决方案开始感到负担更重,开始与我们的许多开发者一起建立功能,我们的许多开发者渴望开始使用新一代的工具,如React.js. 怎么会? 重建往往受到资源的限制,如果不进行重建,就很难合并新旧技术,特别是在视图层.
这个图书馆是试图帮助那些逐渐过渡的人 摆脱像Jade,EJS和 许多其他,对反应(或其他) 类似反应的 libs) (中文(简体) ). 它所做的是提供一套灵活的常规,用于处理特快中通常看到的布局代码,但只要把原始的HTML送去给客户端的任何地方就会起作用.
如果你使用一个温和的库 支持 合并.js这个图书馆对你很有用.
安装
yarn install @artsy/stitch
实例情景 / 本库的目标是谁
- 您的UI 以 Jade/ Pug 或其他基于布局的引擎构建,您使用"块"功能. 如何在保持后向相容性的同时将 React 组件注入到每个组件中?
- 或者,您会使用一个依赖于服务器端的调温库 。 怎样做同样的事情?
- 您的应用程序大部建于 Backbone,每个视图由 Handlebars 模板支持. 你仍然想使用你现有的观点 / UI 同时逐渐向反应移动 如何将每个背骨视图在你的反射组件内“交出”——反之亦然?
- 你已经有了一个服务器侧式,基于EJS的应用程序,你愿意开始利用React的同形"通用"渲染. 如何在服务器上渲染React组件,然后在客户端上再加水?
- 你有一个新的应用软件 完全在React中建立 但出于某种原因 需要保持部分布局相互隔离.
- .
出道后,斯地奇以灵活性为目标.
使用量
目录
- 基本示例
- 运通.js
- 布局和复杂的UI
- Isomorphic / "Universal" 渲染
- 预编模板
- 预览和其他自定义渲染器
<StyledComponents />支助- SSR 中间软件
- 完整 API
- 解决问题
- 发展
(如果你想直接跳进去,看 完整实例项目. ) (中文(简体) )
基本示例
以最基本的形式来说,这个库是一个单一的函数,它接受一条通往您布局的路径,一些数据,并返回一串被缝合的已渲染出,可以发送给客户端的原始html:
<div>
{{title}}
</div>
const html = await stitch({
layout: "templates/layout.handlebars",
data: {
title: "Hello!",
},
})
console.log(html)
// => Outputs:
/*
<div>
Hello!
</div>
*/
通过添加"块",可以开始组装更复杂的布局. 块代表一个模板的路径或返回字符串的函数/反应类:
// templates/layout.handlebars
<html>
<head>
<title>
{{title}}
</title>
</head>
<body
{{{body}}}
</body>
</html>
// index.js
const html = await stitch({
layout: "templates/layout.handlebars",
data: {
title: "Hello World!",
},
blocks: {
body: props => {
return <h1>{props.title}</h1>
},
},
})
console.log(html)
// => Outputs:
/*
<html>
<head>
<title>Hello World!</title>
</head>
<body>
<h1>
Hello World!
</h1>
</body>
</html>
*/
您可以根据需要添加尽可能多的块, 这些块可以通过密钥访问 。 每个块都经过评估和编译,再注入布局. 记住任何接受路径到模板的字段也可以接受一个组件(作为类或无国籍功能组件),模板格式可以混合并匹配. 这允许在看似不相容的取景系统中有很大的灵活性.
运通.js和Pug
记着! 以下每种用途 代理/ 等待,默认可在节点QQ 7.6.0中获取。 如果你的环境还没有支持 Aync / 等待,请查看 解决问题 以下一节以使用ES6承诺为例.
// index.js
import express from "express"
import { stitch } from "@artsy/stitch"
import Body from "./components/Body"
import Footer from "./components/Footer"
const app = express()
app.get("/", async (req, res, next) => {
try {
const html = await stitch({
layout: "templates/mainLayout.pug",
data: {
title: "Hello World!",
description:
"An example showing how to take a view structure based in .pug and interpolate with React",
},
blocks: {
head: "templates/head.pug",
body: Body,
footer: Footer,
},
})
res.send(html)
} catch (error) {
next(error)
}
})
app.listen(3000, () => {
console.log("Listening on port 3000.")
})
//- templates/mainLayout.pug
html
head
!= head
body
!= body
footer
!= footer
//- templates/head.pug
title= title
meta( property='og:title', content= title )
meta( name='description', content= description )
// components/Body.js
export default function Body({ title, description }) {
return (
<div>
<h3>{title}</h3>
<p>{description}</p>
</div>
)
}
// components/Footer.js
export default function Footer() {
return <h4>Hello footer!</h4>
}
布局和其他复杂的 UI 配置
这个系统真正开始发光的地方是 当有一个基础布局被子线扩展。 例如,说您有一个 Express app 上载了一些子应用程序,每个子应用程序都有自己的布局,使用块(或包括等)"扩展"主布局:
//- templates/mainLayout.pug
html
head
include partials/head
block head
body
include partials/body
block body
//- apps/home/templates/homeLayout.pug
extends ../../../templates/mainLayout.pug
block head
!= head
block body
if locals.isAuthenticated
div
| Logged-in
!= body
// apps/home/routes.js
...
const html = await stitch({
basePath: __dirname,
layout: 'templates/homeLayout.pug',
data: {
title: 'Hello how are you?',
description: 'Views extending views extending views...'
},
// Can also define locals, which are passed down to components under
// the `props.locals` key
locals: {
isAuthenticated: req.locals.userIsAuthenticated
},
blocks: {
head: 'templates/head.pug',
body: Body
}
})
res.send(html)
Isomorpic (或"Universal") 渲染
报告更深入地阐述了这一点 异态反应- pug- webpack 示例 但简而言之,由于 data 对象在渲染之前被注入到模板中, 使其在客户端上可用就如同 JSON.stringifying 内容:
// - templates/layout.pug
html
body
script.
var __BOOTSTRAP__ = {JSON.stringify(data)}
!= app
const html = await stitch({
layout: "templates/layout.pug",
blocks: {
app: App,
},
data: {
name: "Z",
},
})
res.send(html)
export default class App extends Component {
componentDidMount () {
console.log(`Hello ${this.props.name}, <App /> is mounted on the client.`)
},
render () {
return (
<div>
Hello {this.props.name}
</div>
)
}
}
// client.js
React.render(<App {...window.__BOOTSTRAP__} />)
预编模板
如果你有一堆老式的 Backbone 视图,你不想在搬去React时扔出去, 但还是想注入他们的标记 输入 您的后期安装到客户端的响应应用程序 ? 这个图书馆很容易:
// apps/login/index.js
...
const html = await stitch({
layout: 'templates/loginLayout.pug',
blocks: {
app: App
},
templates: {
login: 'templates/login.pug'
}
})
// components/Body.js
import React from "react"
import Login from "./Login"
export default function App(props) {
const {
templates: { login },
} = props
return (
<div>
<Login template={login} />
</div>
)
}
// components/Login.js
import React, { Component } from 'react'
import LoginView from '../views/LoginView'
export default class Login extends Component {
componentDidMount () {
this.loginView = new LoginView()
this.loginView.render()
}
componentWillUnmount () {
this.loginView.remove()
}
render () {
return (
<div>
<div dangerouslySetInnerHtml={{
__html: this.props.template
}}>
</div>
)
}
}
// templates/login.handlebars
<div id="login">
<button>
Login
</button>
</div>
// views/LoginView.js
var LoginView = Backbone.View.extend({
el: '#login',
events: {
'click button': 'handleLoginClick'
},
handleLoginClick: function () {
...
},
render: function() {
...
}
})
module.exports = LoginView
模板是预编译的,从您的 React 组件内部可以调取 html , 必要时在 props.templates 键.
自定义渲染器
如果您喜欢使用 React 以外的渲染引擎, 没有问题 -- -- 只要在自定义渲染函数中通过返回字符串:
import renderToString from "preact-render-to-string"
const html = await stitch({
layout: "templates/loginLayout.pug",
config: {
componentRenderer: renderToString,
},
blocks: {
body: App,
},
})
此外,如果您想要覆盖任何默认的模板引擎(例如,将返回什么) require('handlebars')) 您可以通过更新实现 config.engines数字 :
const html = await stitch({
layout: "templates/loginLayout.pug",
config: {
engines: {
handlebars: (filePath, locals) => {
return customHandlebarsRenderer(filePath, locals)
},
pug: (filePath, locals) => {
return customPugRenderer(filePath, locals)
},
},
},
blocks: {
body: App,
},
})
样式组件支持
如果您的 React 应用程序使用 styled-components,确保您安装 bail-plugin 类组件 并允许服务器端通过配置渲染 :
import styled from "styled-components"
const html = await stitch({
layout: "templates/layout.pug",
config: {
styledComponents: true,
},
blocks: {
body: props => {
const Layout = styled.div`
background: purple;
border: 1px solid black;
color: white;
width: 100%;
`
return <Layout>Hello Styled Components!</Layout>
},
},
})
最后,请在您的布局模板中显示您的样式 :
html
head
!= css
body
#react-root
!= body
完整 API
const html = await stitch({
/**
* Sets a base path from which to fetch templates.
*
* @type {String}
*/
basePath: process.cwd(),
/**
* Path to layout template file / component.
*
* @type {String|Component}
*/
layout: <REQUIRED>
/**
* Block sections to pass to template / components. A "block" typically
* represents a discreet section (header, body, footer, etc) but can be
* anything.
*/
blocks: {
/**
* @type {String|Component}
*/
},
/**
* Locals represent Express.js locals as they flow through the `req` / `res`
* cycle. These values could be passed to the following section, `data`, but
* for the sake of clarity and isolation they're typically passed along here.
*
* Accessible from within components via `props.locals` and from within a
* template via `locals`
*/
locals: {
/**
* @type {*}
*/
},
/**
* Data that is passed to templates and components. Embedded directly
* within template code, but also accessible via `data` (useful for
* JSON.stringify'ing and passing down the wire for rehydration). In a
* component, it represents `props` and is accessible as such.
*/
data: {
/**
* @type {*}
*/
},
/**
* Templates / Components that are precompiled and passed along as rendered
* html strings. From within your component, template html is accessible via
* `props.templates`
*/
templates: {
/**
* @type {String|Component}
*/
},
config: {
/**
* Configuration for layout renderer. Right now components are rendered via
* ReactDOM, but any kind of engine can be passed in and accommodated
* assuming it returns a string of rendered markup.
*
* @type {Function}
*/
componentRenderer: ReactDOM.renderToString,
/**
* If you would like to override any default template engines pass in a
* key matching the extension and a function, e.g.,
*
* engines: {
* pug: (filePath, locals) => string
* }
*/
engines: {
/**
* @type {Function}
*/
},
/**
* If your project uses <StyledComponents /> and you would like to extract
* styles from your component during server-side renders, set this to true.
*
* See https://www.styled-components.com/docs/advanced#server-side-rendering
* for more information.
*
* @type {Boolean}
*/
styledComponents: false
}
})
解决问题
帮帮我! 我的观点不能改变,有
Unexpected token (在我的操纵台上.
所有的例子都假设你让 代理/ 等待,在节点的版本中默认出现 7.6.0. 如果你得到这个错误,它很可能是在使用一个尚未支持这个特性的更老版本. 没有恐惧 - 即兴/等待 只是围着承诺的一圈:
stitch({ layout: "layout.ejs" })
.then(html => {
res.send(html)
})
.catch(error => {
next(error)
})
Async / 等待在我的"节点"版本中被支持,但我在浏览器里没有看到任何东西,控制台里也没有出错!
你还记得使用 await 关键字在前面 stitch很容易忘记:
发展
承诺和部署
这个项目使用 自动释放 可以在每张公关上自动发布。 每个公关应该有一个符合以下内容的标签
- 版本: 三维
- 版本: 补丁
- 版本: 次要
- 版本: 主要
没有释放发生在一个 Trivial 更新.