sulu / headless-bundle
提供用于将Sulu作为无头内容管理系统使用的控制器和服务的包装
Requires
- php: ^7.3 || ^8.0
- sulu/sulu: ^2.4 || ^2.5@dev
- symfony/config: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/dependency-injection: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/framework-bundle: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/http-foundation: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/http-kernel: ^4.4 || ^5.4 || ^6.3 || ^7.0
Requires (Dev)
- coduo/php-matcher: ^5.0 || ^6.0
- handcraftedinthealps/code-coverage-checker: ^0.2.1
- handcraftedinthealps/zendsearch: ^2.0
- jackalope/jackalope-doctrine-dbal: ^1.3.4 || ^2.0
- jangregor/phpstan-prophecy: ^1.0
- php-cs-fixer/shim: ^3.0
- phpspec/prophecy: ^1.8
- phpspec/prophecy-phpunit: ^2.0
- phpstan/phpstan: ^1.0
- phpstan/phpstan-doctrine: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpstan/phpstan-symfony: ^1.0
- phpunit/phpunit: ^9.6 || ^10.0
- symfony/browser-kit: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/console: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/dotenv: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/error-handler: ^4.4 || ^5.4 || ^6.3 || ^7.0
- symfony/monolog-bundle: ^3.1
- thecodingmachine/phpstan-strict-rules: ^1.0
Conflicts
- doctrine/orm: < 2.6.3
- zendframework/zend-code: < 3.3.1
README
SuluHeadlessBundle 提供了用于以无头方式使用 Sulu 内容管理系统的控制器和服务。
为此,该包装包括一个控制器,允许检索 Sulu 页面的纯JSON内容。此外,该包装提供API以访问通过传统模板中的Twig扩展提供的功能,例如导航上下文和片段区域。最后,该包装还包括一个可选的 单页应用程序设置,该设置基于React和MobX构建,并利用了该包装的功能。
SuluHeadlessBundle 与Sulu 从版本2.0开始 兼容。查看 composer.json 中的 require 部分,以找到该包装的 最新要求。请注意,此包装仍在开发中,可能尚未涵盖每个用例。根据社区的反馈,该包装的未来的版本可能会包含破坏性更改。
🚀 安装和使用
安装包装
执行以下 composer 命令,将包装添加到项目的依赖项中
composer require sulu/headless-bundle
启用包装
通过将其添加到项目的 config/bundles.php 文件中注册的包装列表中,启用包装
return [ /* ... */ Sulu\Bundle\HeadlessBundle\SuluHeadlessBundle::class => ['all' => true], ];
包含包装的路由
在项目中的新 config/routes/sulu_headless_website.yml 文件中包含包装的路由
sulu_headless: type: portal resource: "@SuluHeadlessBundle/Resources/config/routing_website.yml"
设置模板的控制器
要提供以JSON格式检索页面内容的API,页面模板的控制器必须设置为该包装中包含的 HeadlessWebsiteController
<?xml version="1.0" ?> <template xmlns="..." xmlns:xsi="..." xsi:schemaLocation="..."> <!-- ... --> <controller>Sulu\Bundle\HeadlessBundle\Controller\HeadlessWebsiteController::indexAction</controller> <!-- ... --> </template>
如果通过 {pageUrl}.json 以JSON格式请求页面,则该控制器将提供 页面内容作为JSON对象。
💡 关键概念
使用HeadlessWebsiteController提供页面内容
SuluHeadlessBundle 的主要用例是 将页面内容作为JSON对象提供。可以通过将页面的模板控制器设置为 Sulu\Bundle\WebsiteBundle\Controller\DefaultController::indexAction 来为每个模板单独启用此功能。当使用 HeadlessWebsiteController 作为模板的控制器时,页面内容可通过 {pageUrl}.json 以JSON对象的形式提供。
除了页面内容外,由 HeadlessWebsiteController 返回的JSON对象还包含 元信息,例如页面模板和页面摘要的数据
{
"id": "a5181a5a-b030-4933-b3b0-e9faf7ec756c",
"type": "page",
"template": "headless-template",
"content": {
"title": "Headless Example Page",
"url": "/headless-example",
"contacts": [
{
"id": 416,
"firstName": "Homer",
"lastName": "Simpson",
"fullName": "Homer Simpson",
"title": "Dr. ",
"position": "Nuclear safety Inspector at the Springfield Nuclear Power Plan"
}
]
},
"view": {
"title": [],
"url": [],
"contacts": []
},
"extension": {
"seo": {
"title": "",
"description": "",
"keywords": "",
"canonicalUrl": "",
"noIndex": false,
"noFollow": false,
"hideInSitemap": false
},
"excerpt": {
"title": "",
"more": "",
"description": "",
"categories": [],
"tags": [],
"icon": [],
"images": []
}
},
"author": "2",
"authored": "2019-12-03T11:01:38+0100",
"changer": 2,
"changed": "2020-01-30T07:47:46+0100",
"creator": 2,
"created": "2019-12-03T11:01:38+0100"
}
如果请求使用 HeadlessWebsiteController 的页面内容而不带 .json 后缀,则控制器将渲染设置为页面模板的 view 的Twig模板。在这种情况下,在 .json 请求的情况下将返回的数据可在twig模板中通过一个 headless 变量访问。此行为与默认的Sulu WebsiteController 兼容,并允许在用户初始请求后启动利用SuluHeadlessBundle功能的应用程序。
通过ContentTypeResolver将内容数据解析为标量值
内部,Sulu使用ContentType服务来处理页面修改时的页面内容持久化,以及当页面渲染时解析传递给Twig模板的数据。不幸的是,一些ContentType服务会将非标量值(如媒体实体)传递给Twig模板。由于JSON对象必须只包含标量值,SuluHeadlessBundle不能使用现有的ContentType服务来解析页面内容。
为了解决这个问题,SuluHeadlessBundle引入了ContentTypeResolver服务来解析页面内容为标量值。该组件已经包含了针对各种内容类型的ContentTypeResolver服务。如果你的项目包括自定义内容类型或者你对现有的ContentTypeResolver不满意,你可以通过实现ContentTypeResolverInterface并添加sulu_headless.content_type_resolver标签到服务中来注册自己的ContentTypeResolver。
通过JSON API提供流行的Sulu功能
Sulu内容管理系统提供各种服务和Twig扩展来简化复杂网站的开发和渲染。当以无头方式提供网站内容时,这些功能不可用,因此SuluHeadlessBundle包含控制器来提供用于访问这些功能的JSON API。
API以门户URL注册,因此它们的路径以Web空间的URL为前缀。如果你为你的Web空间配置了特定语言的URL,API URL将类似于以下格式:
https://example.org/en/api/...
导航
/api/navigations/{contextKey}
示例:/api/navigations/main?depth=2&flat=false&excerpt=true
搜索
/api/search?q={searchTerm}
示例:/api/search?q=CMS
片段区域
/api/snippet-areas/{area}
示例:/api/snippet-areas/settings?includeExtension=true
参考单页应用程序实现
SuluHeadlessBundle完全与前端无关,不要求使用特定的技术或框架。尽管如此,该组件在Resources/js-website目录中包含一个独立且可选的单页应用程序设置,允许你快速启动项目,并作为利用组件功能的参考实现。
提供的参考实现基于React作为渲染库,并使用MobX进行状态管理。它围绕一个中央的viewRegistry单例构建,允许你将React组件注册为特定类型资源(例如特定模板的页面)的视图。该应用程序包含一个路由器,它将拦截浏览器的导航,加载请求资源的JSON数据,并用加载数据渲染相应的视图。
要使用提供的单页应用程序设置,你需要在你的Twig模板中包含以下行以初始化和启动应用程序:
{% block content %}
{# ... #}
{# define container element for rendering single page application #}
<div id="sulu-headless-container"></div>
{# initialize application with json data of current page to prevent second request on first load #}
<script>window.SULU_HEADLESS_VIEW_DATA = {{ headless|json_encode|raw }};</script>
<script>window.SULU_HEADLESS_API_ENDPOINT = '{{ sulu_content_path('/api') }}';</script>
{# start single page application by including built javascript code #}
<script src="/build/headless/js/index.js"></script>
{% endblock %}
此外,你还需要将以下文件添加到你的项目中以设置单页应用程序:
assets/headless/package.json
{
"name": "my-frontend-application",
"main": "src/index.js",
"private": true,
"scripts": {
"build": "webpack src/index.js -o ../../public/build/headless/js/index.js --module-bind js=babel-loader -p --display-modules --sort-modules-by size",
"watch": "webpack src/index.js -w -o ../../public/build/headless/js/index.js --module-bind js=babel-loader --mode=development --devtool source-map"
},
"dependencies": {
"sulu-headless-bundle": "file:../../vendor/sulu/headless-bundle/Resources/js-website",
"core-js": "^3.0.0",
"loglevel": "^1.0.0",
"mobx": "^4.0.0",
"mobx-react": "^5.0.0",
"prop-types": "^15.7.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"whatwg-fetch": "^3.0.0",
"history": "^4.10.1"
},
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-decorators": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.8"
}
}
assets/headless/webpack.config.js
const path = require('path'); const nodeModulesPath = path.resolve(__dirname, 'node_modules'); /* eslint-disable-next-line no-unused-vars */ module.exports = (env, argv) => { return { resolve: { modules: [nodeModulesPath, 'node_modules'], }, resolveLoader: { modules: [nodeModulesPath, 'node_modules'], }, }; };
assets/headless/babel.config.js
module.exports = { presets: ['@babel/env', '@babel/react'], plugins: [ ['@babel/plugin-proposal-decorators', {'legacy': true}], ['@babel/plugin-proposal-class-properties', {'loose': true}] ] };
assets/headless/src/index.js
import { startApp } from 'sulu-headless-bundle'; import viewRegistry from 'sulu-headless-bundle/src/registries/viewRegistry'; import HeadlessTemplatePage from './views/HeadlessTemplatePage'; // register views for rendering page templates viewRegistry.add('page', 'headless-template', HeadlessTemplatePage); // register views for rendering article templates // viewRegistry.add('article', 'headless-template', HeadlessTemplateArticle); // start react application in specific DOM element startApp(document.getElementById('sulu-headless-container'));
assets/headless/src/views/HeadlessTemplatePage.js
import React from 'react'; import { observer } from 'mobx-react'; @observer class HeadlessTemplatePage extends React.Component { render() { const serializedData = JSON.stringify(this.props.data, null, 2); return (<pre>{ serializedData }</pre>); } } export default HeadlessTemplatePage;
最后,你可以在assets/headless目录中执行npm install和npm run build来构建你的前端应用程序。
❤️ 支持和贡献
Sulu内容管理系统是一个由各种合作公司支持的社区驱动型开源项目。我们致力于完全透明的开发过程,并非常欢迎任何贡献。
如果您有任何问题,我们很乐意在官方的 Slack频道 中欢迎您。如果您发现了错误或缺少特定功能,请自由地在该 sulu/SuluHeadlessBundle 仓库上 提交新问题,并附上相应的标题和描述。
📘 许可协议
Sulu内容管理系统是根据MIT许可协议发布的。
