diff --git a/.github/workflows/readme-link-check.yml b/.github/workflows/readme-link-check.yml index 919fd97441..5722049bf9 100644 --- a/.github/workflows/readme-link-check.yml +++ b/.github/workflows/readme-link-check.yml @@ -20,4 +20,4 @@ jobs: - name: Check links in README.md with awesome_bot run: | gem install awesome_bot - awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git --files README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/types/README.md,packages/volto-slate/README.md + awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git,https://my-server-DNS-name.tld/api --files README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/types/README.md,packages/volto-slate/README.md,apps/nextjs/README.md,apps/vite-ssr/README.md diff --git a/Makefile b/Makefile index 3ee758c4d0..e4e8146a09 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,8 @@ include variables.mk # You can set these variables from the command line. SPHINXOPTS ?= # Internal variables. -SPHINXBUILD = $(realpath bin/sphinx-build) -SPHINXAUTOBUILD = $(realpath bin/sphinx-autobuild) +SPHINXBUILD = "$(realpath bin/sphinx-build)" +SPHINXAUTOBUILD = "$(realpath bin/sphinx-autobuild)" DOCS_DIR = ./docs/source/ BUILDDIR = ../_build/ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . @@ -26,10 +26,10 @@ VALEFILES := $(shell find $(DOCS_DIR) -type f -name "*.md" -print) # Recipe snippets for reuse -CHECKOUT_BASENAME=$(shell basename $(shell realpath ./)) +CHECKOUT_BASENAME="$(shell basename $(shell realpath ./))" CHECKOUT_BRANCH=$(shell git branch --show-current) CHECKOUT_TMP=../$(CHECKOUT_BASENAME).tmp -CHECKOUT_TMP_ABS=$(shell realpath $(CHECKOUT_TMP)) +CHECKOUT_TMP_ABS="$(shell realpath $(CHECKOUT_TMP))" # We like colors # From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects @@ -189,6 +189,10 @@ copyreleasenotestodocs: start-backend-docker: docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) +.PHONY: start-backend-docker-no-cors +start-backend-docker-nocors: + docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' -e CORS_=true $(DOCKER_IMAGE) + .PHONY: start-frontend-docker start-frontend-docker: docker run -it --rm --name=volto --link backend -p 3000:3000 -e RAZZLE_INTERNAL_API_PATH=http://backend:8080/Plone -e RAZZLE_DEV_PROXY_API_PATH=http://backend:8080/Plone plone/plone-frontend:latest @@ -212,7 +216,7 @@ start-test-acceptance-frontend-dev: build-deps ## Start the Core Acceptance Fron .PHONY: start-test-acceptance-server test-acceptance-server start-test-acceptance-server test-acceptance-server: ## Start Test Acceptance Server Main Fixture (docker container) - docker run -i --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) + docker run -it --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) .PHONY: start-test-acceptance-frontend start-test-acceptance-frontend: build-deps ## Start the Core Acceptance Frontend Fixture @@ -258,7 +262,7 @@ start-test-acceptance-frontend-project: build-deps ## Start the Project Acceptan .PHONY: start-test-acceptance-server-coresandbox test-acceptance-server-coresandbox start-test-acceptance-server-coresandbox test-acceptance-server-coresandbox: ## Start CoreSandbox Test Acceptance Server Fixture (docker container) - docker run -i --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox $(DOCKER_IMAGE_ACCEPTANCE) + docker run -it --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox $(DOCKER_IMAGE_ACCEPTANCE) # ZSERVER_PORT=55001 CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox ./api/bin/robot-server plone.app.robotframework.testing.VOLTO_ROBOT_TESTING .PHONY: start-test-acceptance-frontend-coresandbox @@ -285,7 +289,7 @@ full-test-acceptance-coresandbox: ## Runs CoreSandbox Full Acceptance Testing in .PHONY: start-test-acceptance-server-multilingual test-acceptance-server-multilingual start-test-acceptance-server-multilingual test-acceptance-server-multilingual: ## Start Multilingual Acceptance Server Multilingual Fixture (docker container) - docker run -i --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual $(DOCKER_IMAGE_ACCEPTANCE) + docker run -it --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual $(DOCKER_IMAGE_ACCEPTANCE) .PHONY: start-test-acceptance-frontend-multilingual start-test-acceptance-frontend-multilingual: build-deps ## Start the Multilingual Acceptance Frontend Fixture @@ -307,7 +311,7 @@ full-test-acceptance-multilingual: ## Runs Multilingual Full Acceptance Testing .PHONY: start-test-acceptance-server-seamless-multilingual test-acceptance-server-seamless-multilingual start-test-acceptance-server-seamless-multilingual test-acceptance-server-seamless-multilingual: ## Start Seamless Multilingual Acceptance Server Multilingual Fixture (docker container) - docker run -i --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual $(DOCKER_IMAGE_ACCEPTANCE) + docker run -it --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual $(DOCKER_IMAGE_ACCEPTANCE) .PHONY: start-test-acceptance-frontend-seamless-multilingual start-test-acceptance-frontend-seamless-multilingual: build-deps ## Start the Seamless Multilingual Acceptance Frontend Fixture @@ -329,7 +333,7 @@ full-test-acceptance-seamless-multilingual: ## Runs Seamless Multilingual Full A .PHONY: start-test-acceptance-server-workingcopy test-acceptance-server-workingcopy start-test-acceptance-server-workingcopy test-acceptance-server-workingcopy : ## Start the WorkingCopy Acceptance Server Fixture (docker container) - docker run -i --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage $(DOCKER_IMAGE_ACCEPTANCE) + docker run -it --rm -p 55001:55001 -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage $(DOCKER_IMAGE_ACCEPTANCE) # ZSERVER_PORT=55001 CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.app.iterate,plone.volto,plone.volto.cors APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage ./api/bin/robot-server plone.app.robotframework.testing.VOLTO_ROBOT_TESTING .PHONY: start-test-acceptance-frontend-workingcopy @@ -374,7 +378,7 @@ full-test-acceptance-guillotina: ## Runs the Guillotina Full Acceptance Testing .PHONY: start-test-acceptance-server-5 start-test-acceptance-server-5: ## Start Test Acceptance Server Main Fixture Plone 5 (docker container) - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS5) $(TESTING_ADDONS)' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors $(DOCKER_IMAGE5) ./bin/robot-server plone.app.robotframework.testing.VOLTO_ROBOT_TESTING + docker run -it --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS5) $(TESTING_ADDONS)' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors $(DOCKER_IMAGE5) ./bin/robot-server plone.app.robotframework.testing.VOLTO_ROBOT_TESTING ######### @plone/client diff --git a/apps/nextjs/README.md b/apps/nextjs/README.md index ff8389a364..c881b283fc 100644 --- a/apps/nextjs/README.md +++ b/apps/nextjs/README.md @@ -21,7 +21,11 @@ make start-backend-docker We introduce an environment variable `API_SERVER_URL`. -We have to create this environment variable in the Vercel deployment's control panel, specifying the URL where your backend API server is deployed and the route where the API is located, such as `API_SERVER_URL=https://my_server_DNS_name/api`. +You need to create this environment variable in the Vercel deployment's control panel, specifying the URL where your backend API server is deployed, and the route where the API is located, as shown. + +```shell +API_SERVER_URL=https://my-server-DNS-name.tld/api +``` ### Application rewrite configuragtion @@ -92,8 +96,6 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - ## Learn More To learn more about Next.js, take a look at the following resources: @@ -107,4 +109,4 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new) from the creators of Next.js. -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details. diff --git a/apps/vite-ssr/.gitignore b/apps/vite-ssr/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/apps/vite-ssr/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/apps/vite-ssr/.prettierignore b/apps/vite-ssr/.prettierignore new file mode 100644 index 0000000000..083bdb7c4c --- /dev/null +++ b/apps/vite-ssr/.prettierignore @@ -0,0 +1 @@ +src/routeTree.gen.ts diff --git a/apps/vite-ssr/README.md b/apps/vite-ssr/README.md new file mode 100644 index 0000000000..2894982c36 --- /dev/null +++ b/apps/vite-ssr/README.md @@ -0,0 +1,6 @@ +# Plone on Vite with SSR mode + +This is a proof of concept of a [Vite](https://vitejs.dev) build, using `@plone/client` and `@plone/components` libraries. +This is intended to serve as both a playground for the development of both packages and as a demo of Plone using Vite built with server side rendering (SSR). + +It also uses [TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview) for its routing library. diff --git a/apps/vite-ssr/index.html b/apps/vite-ssr/index.html new file mode 100644 index 0000000000..eecb058a61 --- /dev/null +++ b/apps/vite-ssr/index.html @@ -0,0 +1,12 @@ + + +
+ + + + + + + + + diff --git a/apps/vite-ssr/package.json b/apps/vite-ssr/package.json new file mode 100644 index 0000000000..314a5bebe6 --- /dev/null +++ b/apps/vite-ssr/package.json @@ -0,0 +1,45 @@ +{ + "name": "plone-vite-ssr", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "node server", + "build": "npm run build:client && npm run build:server", + "build:client": "vite build --outDir dist/client", + "build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server", + "serve": "NODE_ENV=production node server", + "debug": "node --inspect-brk server" + }, + "dependencies": { + "@plone/client": "workspace:*", + "@tanstack/react-query": "5.0.5", + "@tanstack/react-router": "^1.16.0", + "@tanstack/react-router-server": "^1.16.0", + "@tanstack/router-devtools": "^1.16.0", + "@tanstack/router-vite-plugin": "^1.16.1", + "axios": "^1.6.5", + "get-port": "^7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sirv": "^2.0.4" + }, + "devDependencies": { + "@babel/core": "^7.23.7", + "@babel/generator": "^7.23.6", + "@rollup/plugin-babel": "^6.0.4", + "@tanstack/react-query-devtools": "^5.20.1", + "@types/express": "^4.17.21", + "@types/react": "^18.2.55", + "@types/react-dom": "^18.2.19", + "@vitejs/plugin-react": "^4", + "compression": "^1.7.4", + "express": "^4.18.2", + "isbot": "^4.3.0", + "node-fetch": "^3.3.2", + "serve-static": "^1.15.0", + "typescript": "^5.3.3", + "vite": "^5.0.12", + "vite-plugin-babel": "^1.2.0" + } +} diff --git a/apps/vite-ssr/server.js b/apps/vite-ssr/server.js new file mode 100644 index 0000000000..d073028c17 --- /dev/null +++ b/apps/vite-ssr/server.js @@ -0,0 +1,100 @@ +import fs from 'node:fs/promises'; +import express from 'express'; +import getPort, { portNumbers } from 'get-port'; + +const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD; + +export async function createServer( + root = process.cwd(), + isProd = process.env.NODE_ENV === 'production', + hmrPort, +) { + const app = express(); + + const prodIndexHtml = isProd + ? await fs.readFile('./dist/client/index.html', 'utf-8') + : ''; + + /** + * @type {import('vite').ViteDevServer} + */ + let vite; + if (!isProd) { + vite = await ( + await import('vite') + ).createServer({ + root, + logLevel: isTest ? 'error' : 'info', + server: { + middlewareMode: true, + watch: { + // During tests we edit the files too fast and sometimes chokidar + // misses change events, so enforce polling for consistency + usePolling: true, + interval: 100, + }, + hmr: { + port: hmrPort, + }, + }, + appType: 'custom', + }); + // use vite's connect instance as middleware + app.use(vite.middlewares); + } else { + const sirv = (await import('sirv')).default; + app.use((await import('compression')).default()); + app.use('/', sirv('./dist/client', { extensions: [] })); + } + + app.use('*', async (req, res) => { + try { + const url = req.originalUrl; + + if (url.includes('.')) { + console.warn(`${url} is not valid router path`); + res.status(404); + res.end(`${url} is not valid router path`); + return; + } + + // Extract the head from vite's index transformation hook + let viteHead = !isProd + ? await vite.transformIndexHtml( + url, + ``, + ) + : prodIndexHtml; + + viteHead = viteHead.substring( + viteHead.indexOf('') + 6, + viteHead.indexOf(''), + ); + + const entry = await (async () => { + if (!isProd) { + return vite.ssrLoadModule('/src/entry-server.tsx'); + } else { + return import('./dist/server/entry-server.js'); + } + })(); + + console.log('Rendering: ', url, '...'); + entry.render({ req, res, url, head: isProd ? viteHead : '' }); + } catch (e) { + !isProd && vite.ssrFixStacktrace(e); + console.log(e.stack); + res.status(500).end(e.stack); + } + }); + + return { app, vite }; +} + +if (!isTest) { + createServer().then(async ({ app }) => + app.listen(await getPort({ port: portNumbers(3000, 3100) }), () => { + console.log('Client Server: http://localhost:3000'); + }), + ); +} diff --git a/apps/vite-ssr/src/config.ts b/apps/vite-ssr/src/config.ts new file mode 100644 index 0000000000..c2f3c47921 --- /dev/null +++ b/apps/vite-ssr/src/config.ts @@ -0,0 +1,9 @@ +const settings = { + apiPath: 'http://localhost:8080/Plone', +}; + +const config = { + settings, +}; + +export default config; diff --git a/apps/vite-ssr/src/entry-client.tsx b/apps/vite-ssr/src/entry-client.tsx new file mode 100644 index 0000000000..ec79c6f65b --- /dev/null +++ b/apps/vite-ssr/src/entry-client.tsx @@ -0,0 +1,8 @@ +import ReactDOM from 'react-dom/client'; + +import { StartClient } from '@tanstack/react-router-server/client'; +import { createRouter } from './router'; + +const router = createRouter(); + +ReactDOM.hydrateRoot(document,