Innovative personalized option for Storybook with a twist - introducing Vite

I am currently developing a unique alternative to Storybook called Storybook-like package named playground. This package is intended to be installed as a dependency into other packages, such as foo-products or bar-products.

My goal is to enable the functionality where running the command playground play from within the foo-products package initiates a Vite app in development mode. This app should utilize components from both the playground and foo-products packages to create a platform for interacting with the components in the foo-products package.

To achieve this, I need to establish a mechanism for seamlessly integrating components from the foo-products package into the Vite app within the playground package.

In my envisioned monorepo setup:

monorepo
- playground
  - cli.js
  - index.html
  - index.ts
  - SiteBuilderModule.ts

- foo-products
  - .play
    - play.config.ts
  - FooModule.ts

The play.config.ts file in the foo-products package will define the functions or components to be utilized on the playground site:

// foo-products: play.config.ts

import { fooFunc } from 'FooModule';

export default {
    functions: [fooFunc, /* ... */]
}

Next, I must find a way to receive these functions in the index.ts file within the playground package:

// playground: index.ts

import { functions } from '...???...'; // Receive `functions` from `foo-products:play.config.ts`

functions.each(func => console.log(func)); // fooFunc, ...

Lastly, the creation of a Vite app when executing the playground play command in the foo-products package necessitates a cli.js. Although unsure about the steps needed, I have referenced code snippets from the Vite documentation.

// playground: cli.js

#!/usr/bin/env node

import { fileURLToPath } from 'node:url'
import { createServer } from 'vite'

console.log('Starting Playground Vite App!');

const __dirname = fileURLToPath(new URL('.', import.meta.url))

const server = await createServer({
  configFile: false,
  root: __dirname,
  server: {
    port: 1337,
  },
})
await server.listen()

server.printUrls()
server.bindCLIShortcuts({ print: true })

The main question now revolves around configuring the cli.js to locate the play.config.ts in the foo-products package, extract the functions, and pass them to the index.ts to facilitate the creation of a multi-package Vite app.

Answer №1

After spending nearly 48 hours diving deep into VitePress and Storybook, I finally cracked the code!

I am once again certain that Vite's Virtual Modules feature is truly remarkable!

Firstly, we established paths to the playground and test-products packages in our CLI js file:

// playground: cli.ts NOTE .ts NOT .js!

const playgroundRoot = path.resolve(fileURLToPath(import.meta.url), '../..');
const productsRoot = process.cwd();

Next, we needed to create a virtual module to access the .play/config.ts from our Vite application:

// playground: plugin.ts

import { type Plugin } from "vite";

export function playConfig(configPath: string): Plugin {
    const moduleId = 'virtual:play-config';

    return {
        name: moduleId,
        resolveId(id) {
            if (id === moduleId)
                return moduleId;
        },
        load(id) {
            if (id === moduleId)
                // We specify where Vite can find the config file.
                // It handles loading and parsing on its own, which is impressive
                return `
                    const playConfig = await import('${configPath}');
                    export default playConfig.default;
                `;
        }
    }
}

Returning to the CLI file, we register our virtual module plugin and provide it with the path to the .play/config.ts file located within the test-products package:

// playground: cli.ts

const playgroundRoot = path.resolve(fileURLToPath(import.meta.url), '../..');
const productsRoot = process.cwd();

const viteServer = await createServer({
    configFile: false,
    root: path.resolve(playgroundRoot, 'app'),
    server: {
        port: 7000
    },
    plugins: [
        playConfig(normalizePath(productsRoot + '/.play/config.ts'))
    ]
});

await viteServer.listen();

viteServer.printUrls();
viteServer.bindCLIShortcuts({ print: true });

That's it! Vite will automatically load everything from .play/config.ts, allowing us to access this data within our Vite application:

// app.ts

import playConfig from 'virtual:play-config';

console.log(playConfig);

In addition, you can explore more advanced examples of using Vite as a "generator tool" in the Vitepress and Storybook repositories. Look for the bin and cli scripts there.

For instance, I recently discovered that Vite offers a loadConfigFromFile function capable of loading configuration files with various extensions (.js, .ts, ...) out of the box!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Learn how to mock asynchronous calls in JavaScript unit testing using Jest

I recently transitioned from Java to TypeScript and am trying to find the equivalent of java junit(Mockito) in TypeScript. In junit, we can define the behavior of dependencies and return responses based on test case demands. Is there a similar way to do t ...

It appears that Type 'MenuItemsProps' does not contain a property named 'map'. This might be causing the error message 'Property 'map' does not exist on

Recently, I delved into learning TypeScript and decided to convert my React code into TypeScript. However, I encountered an issue that left me stumped. I tried passing a state through props to a component with a defined value, hoping that the state would b ...

Objects within objects in TypeScript

Looking to nest JavaScript objects in TypeScript? Here's how: let endpoints = { auth: { login: "http://localhost:8079/auth/login" } }; If you try the following code, it won't work as expected: private endpoints: Object = { ...

Guide to implementing the collapsible start and stop button feature in Angular

Having an issue in my Angular application with the dashboard page. I've created a button for start or stop (toggle functionality) but it's not working as expected. .component.ts toggleCollapse(jammer) { this.jammer.isCollapsed ? 'START& ...

Using Angular Typescript with UWP causes limitations in accessing C# WinRT component classes

Currently, I am working on a UWP application built with Angular5 and I would like to incorporate Windows Runtime Component(Universal) classes into the application to access data from a table. import { Component,OnInit } from '@angular/core'; @C ...

Error TS2339: The type 'never' does not have the property 'getBoundingClientRect'

While optional chaining should suffice, I may have gone a bit overboard in attempting to satisfy TypeScript: const ref = useRef() if (ref !== undefined) { if(ref.hasOwnProperty('current')) { if (ref.current !== undefined ...

Issue with PixiJS: Clicking on a line is disabled after changing its position

Trying to create clickable lines between nodes using Pixi has been a bit of a challenge for me. To ensure the line is clickable, I've extended it in an object that incorporates Container. The process involves finding the angle of the line given two p ...

Is it possible for me to simply return a finally statement from a function that promises a return value?

Here's a function I'm working with: archive = (): ng.IPromise<any> => { var self = this; return self.setStatus() .then( () => { } ) .finally(() => { self.controls = self. ...

Tips for displaying validation error messages in an Angular form

I need help displaying a validation error message for an Angular form. I have three checkboxes and I want to show an error message if none of them are selected. Can anyone provide guidance on how to implement reactive form validation in Angular? Here is a ...

Creating an array with varying types for the first element and remaining elements

Trying to properly define an array structure like this: type HeadItem = { type: "Head" } type RestItem = { type: "Rest" } const myArray = [{ type: "Head" }, { type: "Rest" }, { type: "Rest" }] The number of rest elements can vary, but the first element ...

Uploading and Parsing CSV files in Angular 2

I am new to Angular and I'm working on implementing a feature for uploading and registering content using CSV files. Here is the code snippet: parts.component.html <div class="well"> <form> <div class="row"> <div cl ...

Generate a data type automatically based on an Array

Imagine having an imaginary api that provides color values based on user selections. Consider the following arrays with string values: const Colors1 = ['red', 'blue', 'purple']; const Colors2 = ['blue', 'white& ...

Utilizing a Dependency Injection container effectively

I am venturing into the world of creating a Node.js backend for the first time after previously working with ASP.NET Core. I am interested in utilizing a DI Container and incorporating controllers into my project. In ASP.NET Core, a new instance of the c ...

Having trouble making axios a global instance in my Vue project

Previously, I used to import axios in each Vue component separately when making HTTP requests. An example of this can be seen below: <script lang="ts"> import axios from 'axios'; @Component export default class ExamplePage extend ...

Is there a way to update the data on a view in Angular 9 without the need to manually refresh the page?

Currently, I am storing information in the SessionStorage and attempting to display it in my view. However, there seems to be a timing issue where the HTML rendering happens faster than the asynchronous storage saving process. To better illustrate this com ...

A TypeScript default function that is nested within an interface

This is functioning correctly interface test{ imp():number; } However, attempting to implement a function within the interface may pose some challenges. interface test{ imp():number{ // do something if it is not overwritten } } I am ...

What method is most effective for combining two JSON files in Angular?

My data includes a json file with a product list that looks like this: [{"id":76, "name":"A", "description":"abc", "price":199, "imageUrl":"image.jpg", "productCategory":[{ "categoryId":5, "category":null },{ "categoryId":6, " ...

"Troubiling with preact-cli and Babel/TypeScript configurations for enabling correct functionality of Symbol.iterator and spread syntax

Within my codebase, there exists a function named range that is responsible for generating ranges. The implementation of this function is outlined below: export const range = (min: number, max: number) => { // ... return { // ... ...

What is the best way to iterate over objects within an array in Ionic version 3?

In my array of objects, each object contains another object. I was able to retrieve the value of the supplier's name using the following loop, but it only retrieves the value from one object. I am looking for a way to obtain the supplier's name f ...

Could TypeScript Compiler Overlook Incorrect Type?

Below is a straightforward example. This design follows an MVC architecture: consider M (and its subclasses) as a model and V (and its subclasses) as a view: abstract class M { abstract update() : void; } abstract class V { abstract updateView (m: M) : v ...