@storyblok/astro 4.1.0-next.2 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import type { AstroIntegration, AstroGlobal } from "astro";
1
+ import type { AstroGlobal, AstroIntegration } from "astro";
2
2
  import type { ISbConfig, ISbRichtext, ISbStoriesParams, ISbStoryData, SbRichTextOptions, StoryblokBridgeConfigV2, StoryblokClient } from "./types";
3
- export { storyblokEditable, loadStoryblokBridge, RichTextResolver, RichTextSchema, } from "@storyblok/js";
4
3
  export { handleStoryblokMessage } from "./live-preview/handleStoryblokMessage";
4
+ export { storyblokEditable, loadStoryblokBridge, RichTextResolver, RichTextSchema, } from "@storyblok/js";
5
5
  export declare function useStoryblokApi(): StoryblokClient;
6
6
  export declare function useStoryblok(slug: string, apiOptions: ISbStoriesParams, bridgeOptions: StoryblokBridgeConfigV2, Astro: AstroGlobal): Promise<ISbStoryData<import("@storyblok/js").StoryblokComponentType<string> & {
7
7
  [index: string]: any;
@@ -50,6 +50,10 @@ export type IntegrationOptions = {
50
50
  * Please note: the path takes into account the `componentsDir` option.
51
51
  */
52
52
  customFallbackComponent?: string;
53
+ /**
54
+ * A boolean to enable/disable the Experimental Live Preview feature. Disabled by default.
55
+ */
56
+ livePreview?: boolean;
53
57
  };
54
58
  export default function storyblokIntegration(options: IntegrationOptions): AstroIntegration;
55
59
  export * from "./types";
@@ -1 +1,11 @@
1
- export declare function handleStoryblokMessage(event: any): Promise<void>;
1
+ import type { ISbStoryData } from "@storyblok/js";
2
+ /**
3
+ * Our current tests indicate that the post request section
4
+ * is the bottleneck in terms of time consumption.
5
+ * We should explore alternative methods to optimize
6
+ * this process and improve efficiency.
7
+ */
8
+ export declare function handleStoryblokMessage(event: {
9
+ action: string;
10
+ story: ISbStoryData;
11
+ }): Promise<void>;
@@ -1,3 +1,3 @@
1
- export declare function generateFinalBridgeObject(rawCode: any): {
2
- resolveRelations: any[];
3
- };
1
+ import type { StoryblokBridgeConfigV2 } from "@storyblok/js";
2
+ import type { RawCode } from "../vite-plugins/vite-plugin-storyblok-bridge";
3
+ export declare function generateFinalBridgeObject(rawCode: RawCode): StoryblokBridgeConfigV2;
@@ -1 +1,7 @@
1
- export declare function parseAstRawCode(astCode: any): any;
1
+ import type { RawCodeItemOptions } from "../vite-plugins/vite-plugin-storyblok-bridge";
2
+ import type { Rollup } from "vite";
3
+ /**
4
+ * Parses through the Abstract Syntax Tree (AST) code to locate the 'useStoryblok' function and its properties.
5
+ * This functionality is crucial for generating a virtual module that can be utilized during the initialization of the Storyblok bridge.
6
+ */
7
+ export declare function parseAstRawCode(astCode: Rollup.ProgramNode): RawCodeItemOptions;
@@ -1,2 +1,12 @@
1
+ import type { ISbStoriesParams, StoryblokBridgeConfigV2 } from "@storyblok/js";
1
2
  import type { Plugin } from "vite";
2
- export declare function vitePluginStoryblokBridge(): Plugin;
3
+ export interface RawCodeItem {
4
+ url: string;
5
+ options?: RawCodeItemOptions;
6
+ }
7
+ export type RawCode = RawCodeItem[];
8
+ export interface RawCodeItemOptions {
9
+ apiOptions?: ISbStoriesParams;
10
+ bridgeOptions?: StoryblokBridgeConfigV2;
11
+ }
12
+ export declare function vitePluginStoryblokBridge(experimentalLivePreview: boolean, output: string): Plugin;
@@ -1,57 +1,43 @@
1
- let timeout;
1
+ import type { ISbStoryData } from "@storyblok/js";
2
2
 
3
- export async function handleStoryblokMessage(event) {
3
+ let timeout: NodeJS.Timeout;
4
+ /**
5
+ * Our current tests indicate that the post request section
6
+ * is the bottleneck in terms of time consumption.
7
+ * We should explore alternative methods to optimize
8
+ * this process and improve efficiency.
9
+ */
10
+ export async function handleStoryblokMessage(event: {
11
+ action: string;
12
+ story: ISbStoryData;
13
+ }) {
4
14
  const { action, story } = event || {};
5
15
 
6
16
  if (action === "input" && story) {
7
17
  // Debounce the getNewHTMLBody function
8
18
  const debouncedGetNewHTMLBody = async () => {
9
- const t0 = performance.now();
10
19
  const newBody = await getNewHTMLBody(story);
11
- const t1 = performance.now();
12
- console.log(`getNewHTMLBody took ${t1 - t0} milliseconds.`);
13
20
  const currentBody = document.body;
14
21
  if (newBody.outerHTML === currentBody.outerHTML) return;
15
22
  // Get current focused element in Storyblok
16
23
  const focusedElem = document.querySelector('[data-blok-focused="true"]');
17
24
  updateDOMWithNewBody(currentBody, newBody, focusedElem);
18
- const t2 = performance.now();
19
- console.log(`updateDOMWithNewBody took ${t2 - t1} milliseconds.`);
20
- console.log(`total time took ${t2 - t0} milliseconds.`);
21
25
  };
22
-
23
- // Execute the debounced function after a delay
24
- const debounceDelay = 1500; // Adjust the delay as needed
26
+ const debounceDelay = 500; // Adjust the delay as needed
25
27
  clearTimeout(timeout);
26
28
  timeout = setTimeout(debouncedGetNewHTMLBody, debounceDelay);
27
29
  }
28
30
 
29
- if (["published", "change"].includes(event?.data?.action)) {
31
+ if (["published", "change"].includes(event?.action)) {
30
32
  location.reload();
31
33
  }
32
34
  }
33
35
 
34
- // export async function handleStoryblokMessage(event) {
35
- // const t0 = performance.now();
36
- // const { action, story } = event || {};
37
- // //in the case of input event
38
- // if (action === "input" && story) {
39
- // const newBody = await getNewHTMLBody(story);
40
- // const t1 = performance.now();
41
- // console.log(`getNewHTMLBody took ${t1 - t0} milliseconds.`);
42
- // const currentBody = document.body;
43
- // if (newBody.outerHTML === currentBody.outerHTML) return;
44
- // //Get current focused element in storyblok
45
- // const focusedElem = document.querySelector('[data-blok-focused="true"]');
46
- // updateDOMWithNewBody(currentBody, newBody, focusedElem);
47
- // const t2 = performance.now();
48
- // console.log(`updateDOMWithNewBody took ${t2 - t1} milliseconds.`);
49
- // console.log(`total time took ${t2 - t0} milliseconds.`);
50
- // } else if (["published", "change"].includes(event?.data?.action)) {
51
- // location.reload();
52
- // }
53
- // }
54
- function updateDOMWithNewBody(currentBody, newBody, focusedElem) {
36
+ function updateDOMWithNewBody(
37
+ currentBody: HTMLElement,
38
+ newBody: HTMLElement,
39
+ focusedElem: Element | null
40
+ ) {
55
41
  if (focusedElem) {
56
42
  //Get the [data-blok-uid] of the focused element in storyblok
57
43
  const focusedElementID = focusedElem.getAttribute("data-blok-uid");
@@ -62,21 +48,17 @@ function updateDOMWithNewBody(currentBody, newBody, focusedElem) {
62
48
  if (newDomFocusElem) {
63
49
  // Add the [data-blok-focused] attribute to the above element
64
50
  newDomFocusElem.setAttribute("data-blok-focused", "true");
65
- console.log("Doing partial replace");
51
+ // console.log("Doing partial replace");
66
52
  focusedElem.replaceWith(newDomFocusElem);
67
53
  }
68
54
  } else {
69
- //We can make this part even better
70
- // const allStoryblokElem =
71
- // document.querySelectorAll('[data-blok-uid]')
72
- // console.log({ allStoryblokElem })
73
-
74
- console.log("Doing full replace");
55
+ // console.log("Doing full replace");
75
56
  currentBody.replaceWith(newBody);
76
57
  }
77
58
  }
78
59
 
79
- async function getNewHTMLBody(story) {
60
+ async function getNewHTMLBody(story: ISbStoryData) {
61
+ //TODO How to handel (50x, 405, etc.)
80
62
  const result = await fetch(location.href, {
81
63
  method: "POST",
82
64
  body: JSON.stringify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyblok/astro",
3
- "version": "4.1.0-next.2",
3
+ "version": "4.1.0",
4
4
  "description": "Official Astro integration for the Storyblok Headless CMS",
5
5
  "main": "./dist/storyblok-astro.js",
6
6
  "module": "./dist/storyblok-astro.mjs",
@@ -9,8 +9,7 @@
9
9
  "components",
10
10
  "dev-toolbar",
11
11
  "live-preview",
12
- "utils",
13
- "vite-plugins"
12
+ "utils"
14
13
  ],
15
14
  "exports": {
16
15
  ".": {
@@ -65,10 +64,10 @@
65
64
  "@cypress/vite-dev-server": "^5.0.7",
66
65
  "@rollup/plugin-dynamic-import-vars": "^2.1.2",
67
66
  "@types/lodash.mergewith": "^4.6.9",
68
- "@types/node": "20.12.7",
67
+ "@types/node": "20.12.11",
69
68
  "astro": "^4.6.3",
70
- "cypress": "^13.8.0",
71
- "eslint-plugin-cypress": "^2.15.2",
69
+ "cypress": "^13.8.1",
70
+ "eslint-plugin-cypress": "^3.2.0",
72
71
  "start-server-and-test": "^2.0.3",
73
72
  "typescript": "5.4.5",
74
73
  "vite": "^5.2.10",
@@ -97,6 +96,10 @@
97
96
  {
98
97
  "name": "next",
99
98
  "prerelease": true
99
+ },
100
+ {
101
+ "name": "alpha",
102
+ "prerelease": true
100
103
  }
101
104
  ]
102
105
  },
@@ -1,17 +1,18 @@
1
- export function generateFinalBridgeObject(rawCode: any) {
2
- let mergedOptions = {
1
+ import type { StoryblokBridgeConfigV2 } from "@storyblok/js";
2
+ import type { RawCode } from "../vite-plugins/vite-plugin-storyblok-bridge";
3
+
4
+ export function generateFinalBridgeObject(rawCode: RawCode) {
5
+ let mergedOptions: StoryblokBridgeConfigV2 = {
3
6
  resolveRelations: [],
4
7
  };
5
8
 
6
- function addToResolveRelations(
7
- resolveRelations: string[] | string | undefined
8
- ) {
9
- if (resolveRelations) {
10
- if (Array.isArray(resolveRelations)) {
11
- mergedOptions.resolveRelations.push(...resolveRelations);
12
- } else {
13
- mergedOptions.resolveRelations.push(resolveRelations);
14
- }
9
+ function addToResolveRelations(resolveRelations?: string[] | string) {
10
+ if (resolveRelations && Array.isArray(mergedOptions.resolveRelations)) {
11
+ mergedOptions.resolveRelations.push(
12
+ ...(Array.isArray(resolveRelations)
13
+ ? resolveRelations
14
+ : [resolveRelations])
15
+ );
15
16
  }
16
17
  }
17
18
 
@@ -1,23 +1,37 @@
1
1
  import mergeWith from "lodash.mergewith";
2
+ import type { ISbStoriesParams, StoryblokBridgeConfigV2 } from "@storyblok/js";
3
+ import type { RawCodeItemOptions } from "../vite-plugins/vite-plugin-storyblok-bridge";
4
+ import type { Rollup } from "vite";
5
+ import type { SpreadElement, Property } from "estree";
2
6
 
3
- export function parseAstRawCode(astCode) {
4
- let obj;
5
- function customizer(_, srcValue) {
7
+ /**
8
+ * Parses through the Abstract Syntax Tree (AST) code to locate the 'useStoryblok' function and its properties.
9
+ * This functionality is crucial for generating a virtual module that can be utilized during the initialization of the Storyblok bridge.
10
+ */
11
+
12
+ export function parseAstRawCode(astCode: Rollup.ProgramNode) {
13
+ let obj: RawCodeItemOptions = {};
14
+
15
+ function customizer(_: any, srcValue: any) {
6
16
  if (
7
17
  srcValue?.type === "AwaitExpression" &&
8
- srcValue?.argument?.callee?.name === "useStoryblok"
18
+ srcValue.argument.type === "CallExpression" &&
19
+ srcValue.argument.callee.type === "Identifier" &&
20
+ srcValue.argument.callee.name === "useStoryblok"
9
21
  ) {
10
- const props = srcValue?.argument?.arguments;
11
- if (props[1]?.type === "ObjectExpression") {
22
+ const props = srcValue.argument.arguments;
23
+ if (props && props[1].type === "ObjectExpression") {
24
+ const apiOptions = getAstPropToObj(props[1].properties);
12
25
  obj = {
13
26
  ...obj,
14
- apiOptions: getAstPropToObj(props[1]?.properties),
27
+ apiOptions,
15
28
  };
16
29
  }
17
- if (props[2]?.type === "ObjectExpression") {
30
+ if (props && props[2].type === "ObjectExpression") {
31
+ const bridgeOptions = getAstPropToObj(props[2].properties);
18
32
  obj = {
19
33
  ...obj,
20
- bridgeOptions: getAstPropToObj(props[2]?.properties),
34
+ bridgeOptions,
21
35
  };
22
36
  }
23
37
  }
@@ -27,15 +41,26 @@ export function parseAstRawCode(astCode) {
27
41
  return obj;
28
42
  }
29
43
 
30
- function getAstPropToObj(properties) {
31
- return properties.reduce((options, { key, value }) => {
44
+ function getAstPropToObj(properties: (SpreadElement | Property)[]) {
45
+ const option: ISbStoriesParams | StoryblokBridgeConfigV2 = {};
46
+
47
+ return properties.reduce((options, property) => {
48
+ if (property.type !== "Property") return options;
49
+ const { key, value } = property;
32
50
  const { type } = value;
33
- options[key.name] =
34
- type === "Literal"
35
- ? value.value
36
- : type === "ArrayExpression"
37
- ? value.elements.map((v) => v.value)
38
- : value.value;
51
+ if (key.type !== "Identifier") return options;
52
+
53
+ if (type === "Literal") {
54
+ options[key.name] = value.value;
55
+ } else if (type === "ArrayExpression") {
56
+ const arrayValues = value.elements.reduce((acc, element) => {
57
+ if (element.type === "Literal" && element.value) {
58
+ return [...acc, element.value];
59
+ }
60
+ return acc;
61
+ }, []);
62
+ options[key.name] = arrayValues;
63
+ }
39
64
  return options;
40
- }, {});
65
+ }, option);
41
66
  }
@@ -1,69 +0,0 @@
1
- import type { Plugin } from "vite";
2
- import { parseAstRawCode } from "../utils/parseAstCode";
3
- import { generateFinalBridgeObject } from "../utils/generateFinalBridgeObject";
4
- let previousRawCode = [];
5
-
6
- export function vitePluginStoryblokBridge(): Plugin {
7
- let rawCode = [];
8
- const virtualModuleId = "virtual:storyblok-bridge";
9
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
10
- let _server = null;
11
- let restartTimeout = null;
12
-
13
- return {
14
- name: "vite-plugin-storyblok-bridge",
15
- async resolveId(id: string) {
16
- if (id === virtualModuleId) {
17
- return resolvedVirtualModuleId;
18
- }
19
- },
20
- async transform(code, id) {
21
- if (id.includes("node_modules") && !id.includes("/pages/")) return;
22
- if (!code.includes("useStoryblok")) return;
23
- const moduleInfo = this.getModuleInfo(id);
24
- if (!moduleInfo.meta?.astro) return;
25
- const [, ...routeArray] = id.split("src/pages/");
26
- const url = routeArray.join("/").replace(".astro", "");
27
- const options = parseAstRawCode(this.parse(code));
28
- if (previousRawCode.length) {
29
- rawCode = previousRawCode.filter((i) => i.url !== url);
30
- }
31
- rawCode.push({
32
- url,
33
- options,
34
- });
35
- if (!_server) return;
36
- if (restartTimeout) {
37
- clearTimeout(restartTimeout);
38
- }
39
- restartTimeout = setTimeout(() => {
40
- if (alreadyHaveThisUrl(previousRawCode, rawCode)) return;
41
- if (previousRawCode.length !== 0) {
42
- _server.restart();
43
- console.info("Updating bridge options...");
44
- }
45
- previousRawCode = [...rawCode];
46
- }, 1000);
47
- },
48
- async load(id: string) {
49
- if (id === resolvedVirtualModuleId) {
50
- return `export const bridgeOptions = ${JSON.stringify(generateFinalBridgeObject(rawCode))}`;
51
- }
52
- },
53
- configureServer(server) {
54
- _server = server;
55
- },
56
- };
57
- }
58
-
59
- interface ParsedCodeObj {
60
- url: string;
61
- options: any;
62
- }
63
-
64
- function alreadyHaveThisUrl(a: ParsedCodeObj[] = [], b: ParsedCodeObj[] = []) {
65
- return b.every(({ url, options }) => {
66
- const aCopy = a.find((e) => e?.url === url);
67
- return aCopy && JSON.stringify(options) === JSON.stringify(aCopy.options);
68
- });
69
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * Custom Vite plugin by Tony Sull (https://github.com/tony-sull)
3
- */
4
- import camelcase from "camelcase";
5
- import type { Plugin } from "vite";
6
-
7
- export function vitePluginStoryblokComponents(
8
- componentsDir: string,
9
- components?: object,
10
- enableFallbackComponent?: boolean,
11
- customFallbackComponent?: string
12
- ): Plugin {
13
- const virtualModuleId = "virtual:storyblok-components";
14
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
15
-
16
- return {
17
- name: "vite-plugin-storyblok-components",
18
- async resolveId(id: string) {
19
- if (id === virtualModuleId) {
20
- return resolvedVirtualModuleId;
21
- }
22
- },
23
- async load(id: string) {
24
- if (id === resolvedVirtualModuleId) {
25
- /**
26
- * Handle registered components
27
- */
28
- const imports: string[] = [];
29
- const excludedKeys: string[] = [];
30
- for await (const [key, value] of Object.entries(components)) {
31
- const resolvedId = await this.resolve(
32
- "/" + componentsDir + "/" + value + ".astro"
33
- );
34
-
35
- /**
36
- * if the component cannot be resolved
37
- */
38
- if (!resolvedId) {
39
- if (enableFallbackComponent) {
40
- /**
41
- * if showFallbackComponent is enabled, the current component key needs to be excluded from the imports
42
- * otherwise the attempted import would result in an error
43
- */
44
- excludedKeys.push(key);
45
- } else {
46
- /**
47
- * if it is not enabled, throw a specific error here
48
- */
49
- throw new Error(
50
- `Component could not be found for blok "${key}"! Does "${
51
- "/" + componentsDir + "/" + value
52
- }.astro" exist?`
53
- );
54
- }
55
- } else {
56
- /**
57
- * if the component can be resolved, add it to the imports array
58
- * important: convert blok names to camel case for valid import names
59
- * StoryblokComponent.astro needs to do the same when resolving components!
60
- */
61
- imports.push(`import ${camelcase(key)} from "${resolvedId.id}"`);
62
- }
63
- }
64
-
65
- /**
66
- * Handle custom fallback component
67
- */
68
-
69
- let fallbackComponentKey: string = "";
70
-
71
- if (enableFallbackComponent) {
72
- fallbackComponentKey = ",FallbackComponent";
73
- if (customFallbackComponent) {
74
- /**
75
- * resolve custom FallbackComponent defined in astro.config.mjs
76
- */
77
- const fallbackComponentResolvedId = await this.resolve(
78
- "/" + componentsDir + "/" + customFallbackComponent + ".astro"
79
- );
80
-
81
- if (!fallbackComponentResolvedId) {
82
- throw new Error(
83
- `Custom fallback component could not be found. Does "${
84
- "/" + componentsDir + "/" + customFallbackComponent
85
- }.astro" exist?`
86
- );
87
- }
88
-
89
- imports.push(
90
- `import FallbackComponent from "${fallbackComponentResolvedId.id}"`
91
- );
92
- } else {
93
- /**
94
- * import default FallbackComponent bundled with @storyblok/astro
95
- */
96
- imports.push(
97
- `import FallbackComponent from '@storyblok/astro/FallbackComponent.astro'`
98
- );
99
- }
100
- }
101
-
102
- if (!Object.values(components).length) {
103
- /**
104
- * If no components are registered in astro.config.mjs, either export just the fallback component (default or custom),
105
- * or throw an error.
106
- */
107
- if (enableFallbackComponent) {
108
- return `${
109
- imports[0]
110
- }; export default {${fallbackComponentKey.replace(",", "")}}`;
111
- }
112
- throw new Error(
113
- `Currently, no Storyblok components are registered in astro.config.mjs.\nPlease register your components or enable the fallback component.\nDetailed information can be found here: https://github.com/storyblok/storyblok-astro`
114
- );
115
- } else {
116
- return `${imports.join(";")};export default {${Object.keys(components)
117
- .filter((key) => !excludedKeys.includes(key))
118
- .map((key) => camelcase(key))
119
- .join(",")}${fallbackComponentKey}}`;
120
- }
121
- }
122
- },
123
- };
124
- }
@@ -1,33 +0,0 @@
1
- import type { ISbConfig } from "../types";
2
- import type { Plugin } from "vite";
3
-
4
- export function vitePluginStoryblokInit(
5
- accessToken: string,
6
- useCustomApi: boolean,
7
- apiOptions: ISbConfig
8
- ): Plugin {
9
- const virtualModuleId = "virtual:storyblok-init";
10
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
11
-
12
- return {
13
- name: "vite-plugin-storyblok-init",
14
- async resolveId(id: string) {
15
- if (id === virtualModuleId) {
16
- return resolvedVirtualModuleId;
17
- }
18
- },
19
- async load(id: string) {
20
- if (id === resolvedVirtualModuleId) {
21
- return `
22
- import { storyblokInit, apiPlugin } from "@storyblok/js";
23
- const { storyblokApi } = storyblokInit({
24
- accessToken: "${accessToken}",
25
- use: ${useCustomApi ? "[]" : "[apiPlugin]"},
26
- apiOptions: ${JSON.stringify(apiOptions)},
27
- });
28
- export const storyblokApiInstance = storyblokApi;
29
- `;
30
- }
31
- },
32
- };
33
- }
@@ -1,20 +0,0 @@
1
- import type { Plugin } from "vite";
2
-
3
- export function vitePluginStoryblokOptions(options: object): Plugin {
4
- const virtualModuleId = `virtual:storyblok-options`;
5
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
6
-
7
- return {
8
- name: "vite-plugin-storyblok-options",
9
- async resolveId(id: string) {
10
- if (id === virtualModuleId) {
11
- return resolvedVirtualModuleId;
12
- }
13
- },
14
- async load(id: string) {
15
- if (id === resolvedVirtualModuleId) {
16
- return `export default ${JSON.stringify(options)}`;
17
- }
18
- },
19
- };
20
- }