Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/clear-planets-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@frontify/frontify-cli": major
---

feat: support React 19

The CLI now supports React 19 to stay up-to-date with the React ecosystem and provide better performance and modern features.
Consumers can from now on update their block / theme to React 19.
9 changes: 9 additions & 0 deletions .changeset/sixty-turtles-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@frontify/frontify-cli": major
---

chore: update required node to 22

The minimum required Node.js version has been bumped from 18 to 22.
Node.js 18 is reaching its end-of-life. Node 22 is the current Active LTS release.
Consumers must update their local development environments and CI/CD pipelines to use Node.js version 22 or higher.
9 changes: 9 additions & 0 deletions .changeset/vast-keys-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@frontify/frontify-cli": major
---

feat: build blocks / themes as ESM packages

Custom blocks and themes are now compiled and output as ECMAScript Modules (ESM) instead of relying on global window variables.
ESM is the modern standard for JavaScript, offering better interoperability, tree-shaking and compatibility with modern bundlers.
Consumers don't need to do anything to migrate to ESM, but they should be aware of the changes.
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Prerequisite

- Node >=16
- Node >=22

## Installation

Expand Down
10 changes: 5 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"templates"
],
"engines": {
"node": ">=18"
"node": ">=22"
},
"scripts": {
"build": "vite build",
Expand All @@ -33,9 +33,9 @@
},
"dependencies": {
"@fastify/cors": "^11.2.0",
"@vitejs/plugin-react": "^5.2.0",
"@vitejs/plugin-react": "^6.0.1",
"archiver": "^7.0.1",
"cac": "^6.7.14",
"cac": "^7.0.0",
"conf": "^15.1.0",
"esbuild": "^0.27.4",
"fast-glob": "^3.3.3",
Expand All @@ -45,15 +45,15 @@
"open": "^11.0.0",
"picocolors": "^1.1.1",
"prompts": "^2.4.2",
"vite": "^7.3.1",
"vite": "^8.0.0",
"vite-plugin-externals": "^0.6.2",
"zod": "^3.25.76"
},
"devDependencies": {
"@frontify/eslint-config-basic": "^1.0.8",
"@types/glob-to-regexp": "^0.4.4",
"@types/mock-fs": "^4.13.4",
"@types/node": "^20.19.25",
"@types/node": "^22.19.15",
"@types/prompts": "^2.4.9",
"@types/ws": "8.18.1",
"@vitest/coverage-v8": "4.1.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,17 @@ export const createDeployment = async (
return fastGlob.convertPathToPattern(`${projectPath}/${path}`);
});

const packageJsonContent = reactiveJson<{ dependencies?: Record<string, string> }>(
join(projectPath, 'package.json'),
);

const request = {
build_files: await makeFilesDict(
fastGlob.convertPathToPattern(`${projectPath}/${distPath}`),
buildFilesToIgnore,
),
source_files: await makeFilesDict(fastGlob.convertPathToPattern(projectPath), sourceFilesToIgnore),
dependencies: packageJsonContent?.dependencies || {},
};

if (!dryRun) {
Expand Down
29 changes: 20 additions & 9 deletions packages/cli/src/servers/blockDevelopmentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import react from '@vitejs/plugin-react';
import { createServer } from 'vite';
import { viteExternalsPlugin } from 'vite-plugin-externals';

import pkg from '../../package.json';
import { getAppBridgeVersion } from '../utils/appBridgeVersion';
import { getAppBridgeVersion, getReactVersion } from '../utils/getPackageVersion';
import { reactBareExternalPlugin } from '../utils/vitePlugins';

export class BlockDevelopmentServer {
constructor(
Expand All @@ -16,20 +16,19 @@

async serve(): Promise<void> {
try {
const appBridgeVersion = getAppBridgeVersion(process.cwd());
const reactVersion = getReactVersion(process.cwd());

const viteServer = await createServer({
root: process.cwd(),
plugins: [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
react(),
viteExternalsPlugin({
react: 'React',
'react-dom': 'ReactDOM',
}),
reactBareExternalPlugin(),
],
define: {
'process.env.NODE_ENV': JSON.stringify('development'),
'DevCustomBlock.dependencies.appBridge': JSON.stringify(getAppBridgeVersion(process.cwd())),
},
base: `http://localhost:${this.port}/`,
appType: 'custom',
Expand All @@ -42,6 +41,7 @@
host: this.allowExternal ? '0.0.0.0' : 'localhost',
protocol: 'ws',
},
forwardConsole: false,
},
});

Expand All @@ -50,6 +50,8 @@
return next();
}

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.writeHead(200);
return res.end('OK');
});
Expand All @@ -59,14 +61,23 @@
return next();
}

const host = req.headers.host || `localhost:${this.port}`;
const actualPort = parseInt(host.split(':')[1] || String(this.port), 10);

Check warning on line 65 in packages/cli/src/servers/blockDevelopmentServer.ts

View check run for this annotation

SonarQubeCloud / [CLI] SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=frontify_brand-sdk_cli&issues=AZ0Gq9V6IpZiw5iipWGi&open=AZ0Gq9V6IpZiw5iipWGi&pullRequest=1482

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
return res.end(
JSON.stringify({
url: `http://localhost:${this.port}/${this.entryFilePath}`,
url: `http://${host}/${this.entryFilePath}`,
entryFilePath: this.entryFilePath,
port: this.port,
port: actualPort,
version: pkg.version,
dependencies: {
...(appBridgeVersion ? { '@frontify/app-bridge': appBridgeVersion } : {}),
react: reactVersion,
},
}),
);
});
Expand Down
31 changes: 20 additions & 11 deletions packages/cli/src/servers/themeDevelopmentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import react from '@vitejs/plugin-react';
import { createServer } from 'vite';
import { viteExternalsPlugin } from 'vite-plugin-externals';

import pkg from '../../package.json';
import { getAppBridgeThemeVersion } from '../utils/appBridgeThemeVersion';
import { getAppBridgeThemeVersion, getReactVersion } from '../utils/getPackageVersion';
import { reactBareExternalPlugin } from '../utils/vitePlugins';

export class ThemeDevelopmentServer {
constructor(
Expand All @@ -16,22 +16,19 @@

async serve(): Promise<void> {
try {
const appBridgeThemeVersion = getAppBridgeThemeVersion(process.cwd());
const reactVersion = getReactVersion(process.cwd());

const viteServer = await createServer({
root: process.cwd(),
plugins: [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
react(),
viteExternalsPlugin({
react: 'React',
'react-dom': 'ReactDOM',
}),
reactBareExternalPlugin(),
],
define: {
'process.env.NODE_ENV': JSON.stringify('development'),
'DevCustomTheme.dependencies.appBridgeTheme': JSON.stringify(
getAppBridgeThemeVersion(process.cwd()),
),
},
base: `http://localhost:${this.port}/`,
appType: 'custom',
Expand All @@ -44,6 +41,7 @@
host: this.allowExternal ? '0.0.0.0' : 'localhost',
protocol: 'ws',
},
forwardConsole: false,
},
});

Expand All @@ -52,6 +50,8 @@
return next();
}

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.writeHead(200);
return res.end('OK');
});
Expand All @@ -61,14 +61,23 @@
return next();
}

const host = req.headers.host || `localhost:${this.port}`;
const actualPort = parseInt(host.split(':')[1] || String(this.port), 10);

Check warning on line 65 in packages/cli/src/servers/themeDevelopmentServer.ts

View check run for this annotation

SonarQubeCloud / [CLI] SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=frontify_brand-sdk_cli&issues=AZ0Gq9VyIpZiw5iipWGh&open=AZ0Gq9VyIpZiw5iipWGh&pullRequest=1482

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Content-Type', 'application/json');
res.writeHead(200);
return res.end(
JSON.stringify({
url: `http://localhost:${this.port}/${this.entryFilePath}`,
url: `http://${host}/${this.entryFilePath}`,
entryFilePath: this.entryFilePath,
port: this.port,
port: actualPort,
version: pkg.version,
dependencies: {
...(appBridgeThemeVersion ? { '@frontify/app-bridge-theme': appBridgeThemeVersion } : {}),
react: reactVersion,
},
}),
);
});
Expand Down
11 changes: 0 additions & 11 deletions packages/cli/src/utils/appBridgeVersion.ts

This file was deleted.

44 changes: 20 additions & 24 deletions packages/cli/src/utils/compiler/compileBlock.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
/* (c) Copyright Frontify Ltd., all rights reserved. */

import react from '@vitejs/plugin-react';
import { build } from 'vite';
import { viteExternalsPlugin } from 'vite-plugin-externals';
import { build, esmExternalRequirePlugin } from 'vite';

import { getAppBridgeVersion } from '../appBridgeVersion';
import { REACT_MODULES } from '../vitePlugins';

import { type CompilerOptions } from './compilerOptions';

export const compileBlock = async ({ projectPath, entryFile, outputName }: CompilerOptions) => {
const appBridgeVersion = getAppBridgeVersion(projectPath);
return build({
plugins: [
react(),
viteExternalsPlugin({
react: 'React',
'react-dom': 'ReactDOM',
}),
],
plugins: [react(), esmExternalRequirePlugin({ external: REACT_MODULES })],
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
root: projectPath,
mode: 'production',
build: {
minify: 'terser',
cssMinify: 'lightningcss',
Comment thread
peter-tudosa marked this conversation as resolved.
lib: {
name: outputName,
entry: entryFile,
formats: ['iife'],
formats: ['es'],
fileName: () => 'index.js',
cssFileName: 'style',
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
footer: `
window.${outputName} = ${outputName};
window.${outputName}.dependencies = window.${outputName}.packages || {};
window.${outputName}.dependencies['@frontify/app-bridge'] = '${appBridgeVersion}';
`,
rolldownOptions: {
platform: 'browser',
treeshake: {
// TODO: Fix in Fondue

Check warning on line 31 in packages/cli/src/utils/compiler/compileBlock.ts

View check run for this annotation

SonarQubeCloud / [CLI] SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=frontify_brand-sdk_cli&issues=AZ0Gq9VeIpZiw5iipWGc&open=AZ0Gq9VeIpZiw5iipWGc&pullRequest=1482
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: Fix in Fondue

I assume this one is fixed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I'll have to ask Noah to look into it

moduleSideEffects: [
{ test: /@frontify\/fondue-components/, sideEffects: false },
{ test: /@frontify\/fondue-icons/, sideEffects: false },
{ test: /@frontify\/fondue-tokens/, sideEffects: false },
{ test: /@frontify\/fondue-charts/, sideEffects: false },
{ test: /@frontify\/fondue-rte/, sideEffects: false },
{ test: /\.css$/, sideEffects: true },
],
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/compiler/compilePlatformApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import react from '@vitejs/plugin-react';
import { build } from 'vite';
import { viteExternalsPlugin } from 'vite-plugin-externals';

import { getAppBridgeVersion } from '../appBridgeVersion';
import { getAppBridgeVersion } from '../getPackageVersion';

import { type CompilerOptions } from './compilerOptions';

Expand Down
Loading
Loading