@artsy/stitch

中文文档
6.5.0发布于 2022/04/13MITTypeScriptCommonJS近 30 天下载 8189

帮助您的组件和模板依赖和平共处

中文使用文档
文档目录8 节
  1. 概览
  2. 安装
  3. 实例情景 / 本库的目标是谁
  4. 使用量
  5. 完整 API
  6. 解决问题
  7. 发展
  8. 承诺和部署

@ artsy/ stitch (英语)

Build Status

帮助您的组件和模板依赖和平共处

概览

在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中建立 但出于某种原因 需要保持部分布局相互隔离.
  • .

出道后,斯地奇以灵活性为目标.

使用量

目录

(如果你想直接跳进去,看 完整实例项目. ) (中文(简体) )

基本示例

以最基本的形式来说,这个库是一个单一的函数,它接受一条通往您布局的路径,一些数据,并返回一串被缝合的已渲染出,可以发送给客户端的原始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 更新.

版本与发布记录

@artsy/stitch 共发布 58 个版本,首次发布于 2017/08/26;过去 12 个月发布了 0 个版本。

版本发布日期距今
6.5.0latest2022/04/134 年前
6.4.22021/12/114 年前
6.4.12021/10/205 年前
6.4.02021/09/215 年前
6.3.02021/06/235 年前
6.2.02020/10/316 年前

其他发布通道:canary → 6.1.7-canary.103.1452.0。

关于 @artsy/stitch 的常见问题

@artsy/stitch 是什么?

帮助您的组件和模板依赖和平共处

如何安装 @artsy/stitch?

在项目目录运行 npm install @artsy/stitch,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^16.5.0)、react-dom(^16.5.0)、styled-components(4.0.3)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@artsy/stitch 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@artsy/stitch 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@artsy/stitch 可以商用吗?

@artsy/stitch 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@artsy/stitch 的下载量有多少?

根据 npm 官方下载统计,@artsy/stitch 在 2026/10/07 – 2026/11/05 这 30 天内被下载 8,189 次,日均约 273 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@artsy/stitch 还在维护吗?

最新版本 6.5.0 发布于 2022/04/13(4 年前);至今共发布 58 个版本,其中过去 12 个月发布了 0 个;首次发布于 2017/08/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。