diff --git a/README.md b/README.md index 105fa3a93..739f1f438 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,16 @@ Contributions to the `deco-cx/apps` repository are highly encouraged! We maintai We adhere to **semantic versioning**, and all apps within this repository are versioned collectively using git tags. To release a new version, simply fork the repository, open a pull request, and once approved, request any maintainer to run `deno task release`. There are no strict rules about release frequency, so you can release updates as soon as your changes are merged into the main branch. +### Developing a new app + +Just run: + +```sh +deno task new `APP_NAME` && deno task start +``` + +The app folder and the `deco.ts` will be changed in order to support your newly created app. + ## Transition from deco-sites/std to deco-cx/apps This repository isn't a deco site as it used to be in `deco-sites/std`. In the past, we used `deco-sites/std` as a central hub for various platform integrations. Now, we've modularized these integrations into smaller, installable, and composable apps. These apps now reside in the `deco-cx/apps` repository. Additionally, the `/compat` folder contains two apps, `$live` and `deco-sites/std`, which serve as drop-in replacements for the older apps that contained all blocks together. As users progressively adopt the new apps, they can take full advantage of app extensibility and enhance their websites' capabilities. @@ -46,6 +56,10 @@ For more information, check out our documentation at [https://deco.cx/docs](http | deco-sites/std | An app for compatibility with deco-sites/std app, contains various blocks merged from e-commerce apps. | [manifest](/compat/std/manifest.gen.ts) | | decohub | The best place to find an app for your business case, here is where apps published by any developer in the deco ecosystem will live. | [manifest](/decohub/manifest.gen.ts) | +#### Adding a new app to Deco Hub + +In order to make your app available to be installable in any deco site, just import/export your app inside decohub/apps folder. + ## Thanks to all contributors diff --git a/deco.ts b/deco.ts index 75d354873..ffba7ba39 100644 --- a/deco.ts +++ b/deco.ts @@ -10,6 +10,7 @@ const compatibilityApps = [{ const config = { apps: [ + app("handlebars"), app("vtex"), app("vnda"), app("shopify"), diff --git a/decohub/apps/handlebars.ts b/decohub/apps/handlebars.ts new file mode 100644 index 000000000..21aafaac0 --- /dev/null +++ b/decohub/apps/handlebars.ts @@ -0,0 +1 @@ +export { default } from "../../handlebars/mod.ts"; diff --git a/deno.json b/deno.json index 83218fa94..aeb3fce13 100644 --- a/deno.json +++ b/deno.json @@ -7,7 +7,8 @@ "link": "deno eval 'import \"$live/scripts/apps/link.ts\"'", "unlink": "deno eval 'import \"$live/scripts/apps/unlink.ts\"'", "serve": "deno eval 'import \"$live/scripts/apps/serve.ts\"'", - "update": "deno eval 'import \"$live/scripts/update.ts\"'" + "update": "deno eval 'import \"$live/scripts/update.ts\"'", + "new": "deno run -A ./scripts/new.ts" }, "githooks": { "pre-commit": "check" diff --git a/handlebars/manifest.gen.ts b/handlebars/manifest.gen.ts new file mode 100644 index 000000000..55c1a1d61 --- /dev/null +++ b/handlebars/manifest.gen.ts @@ -0,0 +1,12 @@ +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +const manifest = { + "name": "handlebars", + "baseUrl": import.meta.url, +}; + +export type Manifest = typeof manifest; + +export default manifest; diff --git a/handlebars/mod.ts b/handlebars/mod.ts new file mode 100644 index 000000000..c68fa513e --- /dev/null +++ b/handlebars/mod.ts @@ -0,0 +1,82 @@ +import type { App, AppContext as AC } from "$live/mod.ts"; +import HTMLRenderer from "deco-sites/std/components/HTMLRenderer.tsx"; +import { SourceMap } from "deco/blocks/app.ts"; +import { SectionModule } from "deco/blocks/section.ts"; +import { JSONSchema7 } from "deco/deps.ts"; +import { BlockModuleRef } from "deco/engine/block.ts"; +import Handlebars from "https://esm.sh/v131/handlebars@4.7.8"; +import manifest, { Manifest } from "./manifest.gen.ts"; + +const h = Handlebars as unknown as typeof Handlebars["export="]; +export interface HandlebarSection { + name: string; + /** + * @format html + */ + content: string; +} + +const getHBVars = (content: string) => { + const ast = h.parse(content); + const keys: Record = {}; + + for (const i in ast.body) { + if (ast.body[i].type === "MustacheStatement") { + keys[ + (ast.body[i] as unknown as { path: { original: string } }).path.original + ] = true; + } + } + return Object.keys(keys); +}; +export interface State { + sections: HandlebarSection[]; +} +/** + * @title Build your own Sections + */ +export default function App( + state: State, +): App { + const [sections, sourceMap] = (state.sections ?? []).reduce( + ([currentSections, currentSourceMap], sec) => { + const sectionName = `handlebars/sections/${sec.name}.tsx`; + const compiled = h.compile(sec.content); + const properties: Record = {}; + const vars = getHBVars(sec.content); + for (const key of vars) { + properties[key] = { + type: "string", + }; + } + return [{ + ...currentSections, + [sectionName]: { + default: (props) => { + return HTMLRenderer({ html: compiled(props) }); + }, + } as SectionModule, + }, { + ...currentSourceMap, + [sectionName]: () => { + return Promise.resolve({ + functionRef: sectionName, + inputSchema: { + file: import.meta.url, + name: crypto.randomUUID(), + type: "inline", + value: { + type: "object", + properties, + }, + }, + } as BlockModuleRef); + }, + }]; + }, + [{}, {}] as [Record, SourceMap], + ); + return { manifest: { ...manifest, sections } as Manifest, state, sourceMap }; +} + +export type AppContext = AC>; diff --git a/import_map.json b/import_map.json index 72faadd2b..ea9d8dc81 100644 --- a/import_map.json +++ b/import_map.json @@ -1,6 +1,6 @@ { "imports": { - "$live/": "https://denopkg.com/deco-cx/deco@1.31.3/", + "$live/": "https://denopkg.com/deco-cx/deco@1.32.0/", "$fresh/": "https://denopkg.com/denoland/fresh@1.4.2/", "preact": "https://esm.sh/preact@10.15.1", "preact/": "https://esm.sh/preact@10.15.1/", @@ -10,6 +10,6 @@ "std/": "https://deno.land/std@0.190.0/", "partytown/": "https://deno.land/x/partytown@0.3.4/", "deco-sites/std/": "https://denopkg.com/deco-sites/std@1.20.15/", - "deco/": "https://denopkg.com/deco-cx/deco@1.31.3/" + "deco/": "https://denopkg.com/deco-cx/deco@1.32.0/" } } diff --git a/scripts/new.ts b/scripts/new.ts new file mode 100644 index 000000000..e3dab2da0 --- /dev/null +++ b/scripts/new.ts @@ -0,0 +1,32 @@ +import { join } from "std/path/mod.ts"; + +const appName = Deno.args[0]; +const decoTsPath = join(Deno.cwd(), "deco.ts"); +const decoTs = await Deno.readTextFile(decoTsPath); + +await Deno.mkdir(join(Deno.cwd(), appName)); +await Deno.writeTextFile( + decoTsPath, + decoTs.replace(` apps: [`, ` apps: [\n app("${appName}"),`), +); + +await Deno.writeTextFile( + join(Deno.cwd(), appName, "mod.ts"), + ` +import type { App, AppContext as AC } from "$live/mod.ts"; +import manifest, { Manifest } from "./manifest.gen.ts"; + +// deno-lint-ignore ban-types +export type State = {}; +/** + * @title ${appName} + */ +export default function App( + state: State, +): App { + return { manifest, state }; +} + +export type AppContext = AC>; +`, +);