GithubHelp home page GithubHelp logo

tajo / ladle Goto Github PK

View Code? Open in Web Editor NEW
2.5K 6.0 83.0 25.18 MB

๐Ÿฅ„ Develop, test and document your React story components faster.

Home Page: https://www.ladle.dev

License: MIT License

JavaScript 26.92% HTML 0.19% TypeScript 64.15% CSS 3.16% Shell 0.09% AppleScript 0.50% Dockerfile 0.55% MDX 4.45%
playground react javascript testing components documentation styleguide typescript ui esbuild

ladle's People

Contributors

aksata7 avatar akx avatar andrewbrey avatar azuline avatar beckend avatar calloc134 avatar chasestarr avatar cm-dyoshikawa avatar ecstrema avatar eecavanna avatar frehner avatar georgenagel avatar kasbah avatar kazuma1989 avatar matsebassen avatar matthewwolfe avatar moltar avatar nemzes avatar nulladdict avatar pan93412 avatar pdeslaur avatar roeefl avatar shinyaigeek avatar simomoilanen avatar smajl avatar tajo avatar talshani avatar tobeycodes avatar uyeong avatar wojtekmaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ladle's Issues

Is the public repository served?

I store the font files as local assets under the /public directory, but when I try to import them from stories I get a "failed to load resources" error.

How to use .env variables?

Ladle looks great, trying it as a testbed for playwright. Is there any way to access the variables in the .env file? I noticed that doing something like VITE_VAR=foo yarn ladle serve works.

Stories do not auto reload

This is looking great so far, great work.

One issue, if I update a story file (e.g. by adding a new export) ladle does not auto refresh.
However, editing a component that the story references does cause a reload.

Have I missed a setting that would stop edits to a stories.tsx file from refreshing?

HMR not working as expected

Describe the bug
When developing components, Ladle does not detect correctly the changes. Cloning ladle repo and playing around with the example react project leads to this same issue.

Also, something I've noticed on the source code is that it would only watch for .stories. and does not respect the glob we override (e.g: if I want my stories to have .fixture instead)

To Reproduce

  • Open StackBlitz example
  • Change the component title constant or something else on the react component (in this particular use case we are trying to change in an existing story, not a new story file)

Expected behavior
When modifying a react component/source code it should reflect those changes, right now we only see Vite detecting those changes but nothing is actually propagated to the DOM.

Desktop (please complete the following information):
System:
OS: macOS 12.3.1
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 23.91 GB / 64.00 GB
Shell: 3.2.2 - /usr/local/bin/fish
Binaries:
Node: 16.15.0 - ~/.volta/tools/image/node/16.15.0/bin/node
Yarn: 1.22.18 - ~/.volta/tools/image/yarn/1.22.18/bin/yarn
npm: 8.5.5 - ~/.volta/tools/image/node/16.15.0/bin/npm
Browsers:
Chrome: 101.0.4951.54
Safari: 15.4

No stories found message

Currently I've installed the version 0.9.0 and inserted the configuration file on .ladle folder

export default {
stories: "../src/**/control.stories.{js,jsx,ts,tsx}",
};

the react version used was ^17.0.2, but on current run shows " No stories found" message,

Ps. I'm using same config on working storybook files

Do not normalise storyName

image

I think we should only normalise if it's from filename, if we set it explicitly by title/storyName we should use it directly

Css/Scss modules support for camel case

For our aplication with css modules we enabled in webpack
exportLocalsConvention as "camelCase"
This results css like

.red-arrow {}
in import as
styles.redArrow

ladle does not seem to support this yet

Lodash Vite error - `Two output files share the same path but have different contents`

My team is interested in using this project for React component development and to build a component library.

The Problem

When onboarding to this project, we hit the following errors when running npm run ladle serve:

9:18:08 PM [vite] error while updating dependencies:
Error: Build failed with 2 errors:
error: Two output files share the same path but have different contents: node_modules/.vite/processing/lodash_cloneDeep.js.map`
error: Two output files share the same path but have different contents: node_modules/.vite/processing/lodash_cloneDeep.js

The reason for this is this project depending on and importing lodash.clonedeep. Our project uses and imports lodash/cloneDeep.

Within the ladle project, optimizeDeps.include is configured to optimize lodash.clonedeep. This leads to an error when building via Vite. This problem has been documented before in this StackOverflow post.

If I comment out this line in the built ladle project locally, our Ladle component previews work seamlessly.

Ideas

lodash/** import usage is common in projects today, and can easily conflict with the optimized dependencies configured currently in the ladle project. I see 2 alternatives:

  1. Remove lodash.** dependency optimizations from ladle.
  2. Enable users of ladle to overwrite optimizeDeps.include, vs. strictly enabling adding to this list (this is a restriction in the configuration API today to my understanding).

Finally....

I'm happy to help push a solution to this problem forward. I wanted to get a sense for what solution we prefer before I cut a PR. Would love thoughts!

cc: @tajo

import aliases?

in my tsconfig I have

{
    "baseUrl": ".",
    "paths": {
      "~/*": ["./app/*"]
    },
}

and out of the box, when importing stories that use this import format I get "failed to resolve import" errors, which makes sense

seeing as ladle is based on vite, I looked up how to enable import aliases through vite, and found I can create a vite.config.js like so:

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  resolve: {
    alias: {
      '~': path.resolve(__dirname, './app'),
    },
  },
})

however this doesn't seem to have any effect on import resolution in ladle and I still get the same error

11:50:35 AM [vite] Internal server error: Failed to resolve import "~/utils/classNames" from "app/components/Forms.tsx". Does the file exist?

Title "must be a string literal."

title and component.storyName doesn't allow to be set as variable.

const foo = 'Level / Sub level';

export default {
    title: foo,
    // Error: Can't parse the default title and meta of src/basic.stories.tsx. 
    // Meta must be serializable and title a string literal.
};

export const Button = () => <button>My Button</button>;

// Button.storyName = foo;
// Error: Button.storyName in src/basic.stories.tsx must be a string literal.

https://stackblitz.com/edit/ladle-bp6tgk?file=src%2Fbasic.stories.tsx

Update Reach components to support React 18

While Ladle supports React 18, Reach Dialog (its dependency) needs to be update as well to satisfy more strict pnpm.

packages/my-package
โ””โ”€โ”ฌ @ladle/react
  โ””โ”€โ”ฌ @reach/dialog
    โ”œโ”€โ”€ โœ• unmet peer react@"^16.8.0 || 17.x": found 18.0.0
    โ”œโ”€โ”€ โœ• unmet peer react-dom@"^16.8.0 || 17.x": found 18.0.0
    โ”œโ”€โ”ฌ @reach/portal
    โ”‚ โ”œโ”€โ”€ โœ• unmet peer react@"^16.8.0 || 17.x": found 18.0.0
    โ”‚ โ”œโ”€โ”€ โœ• unmet peer react-dom@"^16.8.0 || 17.x": found 18.0.0
    โ”‚ โ””โ”€โ”ฌ @reach/utils
    โ”‚   โ”œโ”€โ”€ โœ• unmet peer react@"^16.8.0 || 17.x": found 18.0.0
    โ”‚   โ””โ”€โ”€ โœ• unmet peer react-dom@"^16.8.0 || 17.x": found 18.0.0
    โ””โ”€โ”ฌ react-focus-lock
      โ””โ”€โ”ฌ react-clientside-effect
        โ””โ”€โ”€ โœ• unmet peer react@"^15.3.0 || ^16.0.0 || ^17.0.0": found 18.0.0

Can't get Ladle to find stories that are in a sibling directory

Describe the bug
I'm working on a design system, and would like to replace Storybook with Ladle.
I've put together an MVP of the folder structure here: https://github.com/lukebennett88/ladle-test
Basically I want to configure Ladle inside the /docs folder, but I want it to go up to the /packages folder to look for .story.tsx files. I can't seem to get Ladle to find and files. If I set it up in the root it does work, but that's not what I want.

To Reproduce

Not sure Stackblitz works with monorepos, but here is a link:
https://stackblitz.com/github/lukebennett88/ladle-test

Steps to reproduce the behavior:

Expected behavior

  1. cd docs
  2. yarn ladle:serve

CleanShot 2022-05-10 at 15 48 31@2x

Desktop (please complete the following information):

  • OS: macOS 12.3.1

GlobalProvider type does not have 'children' property

The providers section has an example that describes how to add a global provider:

import type { GlobalProvider } from "@ladle/react";

export const Provider: GlobalProvider = ({ children, globalState }) => (
  <>
    <h1>Theme: {globalState.theme}</h1>
    {children}
  </>
);

My components file in .ladle/components.tsx is a typescript file. The children property is not declared in the GlobalProvider type, because of this, my IDE gives me a Property 'children' does not exist on type '{ globalState: GlobalState; }'. error.

Here's what the GlobalProvider looks like:

export type GlobalProvider = React.FC<{ globalState: GlobalState }>;

Since the example is using TS and it also passes in a children prop, the GlobalProvider type should have a children prop.

Screenshot 2022-04-20 at 09 53 08

Radio ArgTypes for story controls not working on latest version

In a fork of the default stackblitz, I am unable to use the radio button as documented in controls docs

demo recreation

export const Button = ({variant}: {variant: string}) => <button>Button {variant}</button>;

Button.argTypes = {
  variant: {
    options: ["primary", "secondary"],
    control: { type: "radio" },
    defaultValue: "primary",
  },
};

Error message I got in the console

Uncaught TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at args-provider.tsx:13:12

chunk-M5UXS5QK.js?v=09754c74:13357 The above error occurred in the <ArgsProvider> component:

    at ArgsProvider (https://ladle-a4m3ug--4123.local.webcontainer.io/src/args-provider.tsx:4:25)
    at Provider (https://ladle-a4m3ug--4123.local.webcontainer.io/generated/generated-list.tsx:11:28)
    at Suspense
    at ErrorBoundary (https://ladle-a4m3ug--4123.local.webcontainer.io/src/error-boundary.tsx:4:5)
    at Story (https://ladle-a4m3ug--4123.local.webcontainer.io/src/story.tsx:11:18)
    at main
    at App (https://ladle-a4m3ug--4123.local.webcontainer.io/src/app.tsx:24:41)

React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

"Show code" option

It would be nice if we could have an option to see the source code for the currently viewed component, in its current state (determined by props passed to it).

I suppose it could be a button next to configuration button.

I have no idea how to do this, but if I were to start, perhaps I would leverage Espree's parse function to find a given named export value before parsing, as-is in the actual story file ๐Ÿค”

No custom config found

Description

After installing @ladle/react-cjs I created a custom config at .ladle/config.mjs with

export default {
  stories: 'webpack/ui/components/**/stories.{js,jsx}',
  root: '../',
  ...
}

When I'm running the ladle with yarn ladle serve in the opened window I can see that the configured glob pattern for stories is the same as I specified in config file, but no stories found. And if I run ladle with debug mode I can see that CLI says that no custom config found

[DISCUSSION] Add Docs views like storybook ?

First of all, thanks for working on this project, this looks promising !

I am unable to find a way to view the docs and code section of the stories. Do we have plans to them in the future ?

Would love to contribute on that.

Any help on this would be really appreciated, TIA.

Global component args

Hi,
is there the possibility to apply arguments to the .ladle/components.tsx component?
I'm using a global context provider, which allows to change theme and language.
Therefore it would be great to toggle globally this properties.

Thank you for your great project!

CSF is great, but it lacks an important feature for visual testing

Is your feature request related to a problem? Please describe.

We heavily use Storybook in Wix for screenshot testing our library components. What current storybook supports and will not deprecate until find a solution is storiesOf function where you can generate a bunch of stories by using arrays of objects. With CSF you cannot achieve this.

Consider this story file as a way to render props permutation test stories:

const tests = [
  {
    describe: 'alignItems',
    its: [
      {
        it: 'center',
        props: {
          alignItems: 'center',
        },
      },
      {
        it: 'right',
        props: {
          alignItems: 'right',
        },
      },
      {
        it: 'left',
        props: {
          alignItems: 'left',
        },
      },
    ],
  },
  
  tests.forEach(({ describe, its }) => {
  its.forEach(({ it, props }) => {
    storiesOf(`AddItem${describe ? '/' + describe : ''}`, module).add(
      it,
      () => <AddItem {...defaultProps} {...props} />,
    );
  });
});

This generates 3 stories and adding another one is easy for the next contributor.

Describe the solution you'd like

I think just by supporting similar method like storiesOf ladle could be used for such cases or alternative solution that would support generating bunch of stories in a file based on some data structure.

Describe alternatives you've considered

For now storybook is the clear alternative or we would need to create some sort of story files with CSF generator based on mentioned data structure.

[PROPOSAL]: New layout

Hey ๐Ÿ‘‹

I love Ladle already. Its focus on performance is something I always wanted to see in Storybook. So, I wanna help it give a step forward to have a UI that matches its awesome engineering layers.

Here's is the design I made:

Light
Light

Dark
Dark

Here are the steps I imagined for this project:

  • Design the new UI
  • Collect feedbacks
  • Implement changes on the design after feedback
  • Start PR with the new layout
  • Finish first version of PR
  • Get the PR Reviewed
  • Implement requested changes
  • Get the PR merged

If this layout is not what you expect, please, feel free to express your feelings.
Thanks for the attetion.

UPDATE
The PR is now open #101 as a draft

Unable to import some Vite plugins in Ladle's configuration: Invalid left-hand side in assignment

Brief Description

As ladle's config.mjs will also be used in the browser environment, which did not support the Vite-only JavaScript syntax, it will throw this when importing some Vite plugins such as vite-plugin-svg-icons:

Uncaught SyntaxError: Invalid left-hand side in assignment

Reproduce Steps

Possible Solution

Separate the configuration for Vite from config.mjs to vite-config.mjs, so we only import Vite-specific plugins in the Vite environment.

CSS Modules doesn't get inserted in the build output

Problem description

CSS modules don't get inserted in the final build output (with ladle build).


Reproduction code

https://stackblitz.com/edit/ladle-nmhbpc

Short instructions and observations

  1. โœ… Using npm run dev (ladle serve), you can see the code working as excepted.
    Tested with *.css and *.scss file extension.

  2. โŒ Using npm run build & npm run serve, the CSS modules don't work.
    The output file ./build/assets/basic.stories.<hash_id>.css aren't being included at all, anywhere. I was excepting to see it being included in the <head> tag.
    On the other hand, the JS module ./build/assets/basic.stories.<hash_id>.js do have correct generated CSS module assigned to the components property className.

Single story hoisting

Just tried out Ladle, that what I wanted to build years ago because SB is too slow and the bundle size is too big

Single story hoisting is the one I missed most

another one is how to inject preview header? I use it to inject google fonts, it would be nice that we can customise the layout

ladle not flow vite / tsconfig alias path

errro info like:

[plugin:vite:import-analysis] Failed to resolve import "@/config/config" from "src/apktype/hooks/asyncApktypeAction.ts". Does the file exist?
[/Users/qin.....................e/hooks/asyncApktypeAction.ts:6:0]()
3  |  import request from "umi-request";
4  |  import { getApktpeListApi } from "./hooks";
5  |  import { baseURL } from "@/config/config";
   |                           ^
6  |  const oneAtom = atom(false);
7  |  export const asyncApktypeAction = () => {
....

tsconfig

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "types": ["vite/client", "node"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*"],
      "@/*": ["src/*"]
    }
  },
  "include": ["src", "vite.config.ts", "cypress"]
}

vite config

import react from "@vitejs/plugin-react";
import { resolve } from "path";
import { visualizer } from "rollup-plugin-visualizer";
import { UserConfig } from "vite";
import Checker from "vite-plugin-checker";

function pathResolve(dir: string) {
  return resolve(__dirname, ".", dir);
}

const shouldAnalyze = process.env.ANALYZE;

const config: UserConfig = {
  resolve: {
    alias: [
      {
        find: /@\//,
        replacement: pathResolve("src") + "/"
      }
    ]
  },
  build: {
    rollupOptions: {
      plugins: !!shouldAnalyze ? [visualizer({ open: true, filename: "./bundle-size/bundle.html" })] : []
    },
    sourcemap: !!shouldAnalyze
  },
  css:{
    preprocessorOptions:{
      less: {
        javascriptEnabled: true,
      }
    }

  },
  plugins: [
    react({}),
    Checker({
      typescript: true,
      overlay: true,
      eslint: {
        files: "src",
        extensions: [".ts", ".tsx"]
      }
    })
  ],
};

const getConfig = () => config;

export default getConfig;

it's a bug ?

Imported CSS messes up with Ladle's sidebar

I just imported my CSS file like this: import "./styles.css"; and now the elements in the sidebar are misaligned.
Is there any way the imported styles.css can be applied only inside .ladle-main?

Screen Shot 2022-03-30 at 10 31 36 PM

MDX support

Hello! It's mentioned in the docs that you have plans to add support for MDX to Ladle. Our team uses Storybook but we don't use CSF, rather we write all of our component stories in MDX and basically just use Storybook for rendering and building the stories.

Just out of curiosity, what would support for MDX look like in Ladle? Is this work already ongoing?

Thanks!

`--open` always created a new tab rather than re-using existing

Describe the bug
Currently when we pass --open it will always create a new tab instead of re-using the existing tab(s)

To Reproduce
In any integration with Ladle, this is part of the core

Steps to reproduce the behavior:

  1. Create a new ladle project/use any existing
  2. Simply pass --open and it will create a new tab each time we start the server

Expected behavior
If there's already a tab on that page, simply refresh that page

@tajo I'm able to create a PR to address this

Desktop (please complete the following information):
System:
OS: macOS 12.3.1
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 18.95 GB / 64.00 GB
Shell: 3.2.2 - /usr/local/bin/fish
Binaries:
Node: 14.18.3 - ~/.nvm/versions/node/v14.18.3/bin/node
Yarn: 1.22.10 - /usr/local/bin/yarn
npm: 6.14.15 - ~/.nvm/versions/node/v14.18.3/bin/npm
Browsers:
Chrome: 100.0.4896.127
Safari: 15.4

Type checking stories

Storybook supports a typescript: { check: true } config option, which type checks the stories when building/developing the stories, and not only when opening a story in VSCode.

This is very helpful, and would improve Ladle a lot.

Windows 11: Weird path resolving issue

Decided to give ladle a try on a new project. It worked fine until I tried to add a .ladle/ dir for some config bits. Below is the result of running yarn ladle serve. Notice in particular that the config.mjs isn't detected, and the path .............ladleconfig.mjs seems out of nowhere. Running on Windows 11, if that makes a difference.

โžœ node -v
v16.13.2

โžœ ls .ladle

    Directory: C:\dev123\packages\frontend\.ladle

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           3/22/2022  5:02 PM            203 components.tsx
-a---           3/22/2022  5:08 PM             53 config.mjs

โžœ cat .ladle/config.mjs 
export default {
        "serve":{
                "port": 6000
        }
}

โžœ yarn dev:ladle
yarn run v1.22.17
$ cross-env DEBUG=ladle* ladle serve .
  ladle:cli Starting serve command +0ms
  ladle:cli CLI theme: undefined +1ms
  ladle:cli CLI stories: undefined +0ms
  ladle:cli CLI port: undefined +0ms
  ladle:cli CLI open: undefined +0ms
  ladle:cli No custom config found. +1ms
  ladle:cli Final config:
  ladle:cli {
  ladle:cli   "stories": "src/**/*.stories.{js,jsx,ts,tsx}",
  ladle:cli   "root": "C:\\dev123\\packages\\frontend",
  ladle:cli   "defaultStory": "",
  ladle:cli   "babelPresets": [],
  ladle:cli   "babelPlugins": [],
  ladle:cli   "define": {},
  ladle:cli   "envPrefix": "VITE_",
  ladle:cli   "resolve": {
  ladle:cli     "alias": {}
  ladle:cli   },
  ladle:cli   "optimizeDeps": {
  ladle:cli     "include": []
  ladle:cli   },
  ladle:cli   "addons": {
  ladle:cli     "control": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": {}
  ladle:cli     },
  ladle:cli     "theme": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": "light"
  ladle:cli     },
  ladle:cli     "mode": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": "full"
  ladle:cli     },
  ladle:cli     "rtl": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": false
  ladle:cli     },
  ladle:cli     "ladle": {
  ladle:cli       "enabled": true
  ladle:cli     }
  ladle:cli   },
  ladle:cli   "serve": {
  ladle:cli     "open": "**Default**",
  ladle:cli     "port": 61000,
  ladle:cli     "define": {}
  ladle:cli   },
  ladle:cli   "build": {
  ladle:cli     "out": "build",
  ladle:cli     "sourcemap": false,
  ladle:cli     "baseUrl": "/",
  ladle:cli     "define": {}
  ladle:cli   }
  ladle:cli } +0ms
  ladle:cli Port set to: 61000 +5ms

   โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
   โ”‚                                                   โ”‚
   โ”‚   ๐Ÿฅ„ Ladle.dev served at http://localhost:61000   โ”‚
   โ”‚                                                   โ”‚
   โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

  ladle:vite transforming: C:/dev123/packages/frontend/node_modules/@ladle/react/lib/app/generated/generated-list.tsx +0ms
  ladle:vite Initial generation of the list +0ms
  ladle:vite Parsing src/app/components/UserIcon/UserIcon.stories.tsx +0ms
  ladle:vite Parsed data for src/app/components/UserIcon/UserIcon.stories.tsx: +61ms
  ladle:vite {
  ladle:vite   entry: 'src/app/components/UserIcon/UserIcon.stories.tsx',
  ladle:vite   stories: [
  ladle:vite     {
  ladle:vite       storyId: 'user-icon--default',
  ladle:vite       componentName: 'user$icon$$default',
  ladle:vite       namedExport: 'Default',
  ladle:vite       locStart: 3,
  ladle:vite       locEnd: 3
  ladle:vite     }
  ladle:vite   ],
  ladle:vite   exportDefaultProps: { title: undefined, meta: undefined },
  ladle:vite   namedExportToMeta: {},
  ladle:vite   namedExportToStoryName: {},
  ladle:vite   storyParams: {},
  ladle:vite   fileId: 'UserIcon'
  ladle:vite } +1ms
  ladle:vite C:\dev123\packages\frontend\.ladle/components.tsx found. +0ms
  ladle:vite Custom provider found. +1ms
Failed to resolve import ".............ladleconfig.mjs" from "node_modules\@ladle\react\lib\app\generated\generated-list.tsx". Does the file exist?
Failed to resolve import ".............ladleconfig.mjs" from "node_modules\@ladle\react\lib\app\generated\generated-list.tsx". Does the file exist? (x2)     
5:22:52 PM [vite] Internal server error: Failed to resolve import ".............ladleconfig.mjs" from "node_modules\@ladle\react\lib\app\generated\generated-list.tsx". Does the file exist?
  Plugin: vite:import-analysis
  File: C:/dev123/packages/frontend/node_modules/@ladle/react/lib/app/generated/generated-list.tsx
  6  |  export let stories = { "user-icon--default": { component: user$icon$$default } };
  7  |  export let config = {};
  8  |  import customConfig from ".............ladleconfig.mjs";
     |                            ^
  9  |  config = customConfig;
  10 |  import { Provider as CustomProvider } from ".............ladlecomponents.tsx";
      at formatError (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:36027:46)
      at TransformContext.error (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:36023:19)
      at normalizeUrl (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:56731:26)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async TransformContext.transform (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:56880:57)
      at async Object.transform (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:36262:30)
      at async doTransform (C:\dev123\packages\frontend\node_modules\@ladle\react\node_modules\vite\dist\node\chunks\dep-5a245411.js:52681:29)
Failed to resolve import ".............ladleconfig.mjs" from "node_modules\@ladle\react\lib\app\generated\generated-list.tsx". Does the file exist?

Support css.preprocessorOptions

Is your feature request related to a problem? Please describe.
Support css.preprocessorOptions as done in Vite.

Describe the solution you'd like
See above

โ€”

I think having the capacity to provide a middleware function to the config, e.g.

config: currentConfig => newConfig,

that will transform the vite config produced by vite-dev, vite-prod would enable developers to do all they need with the Vite config.

Storybook does the same with its webpack config.

Path Resolving

It would be cool if we could pass alias object in the same way as define (ladleConfig.define).
Many of my projects depends on path resolving, as I can easily access nested files across project.

If I am not mistaken, the first code block should work just fine, but I've added a second solution as I don't work Vite quite often.
I would try to make a pull request, but I am not sure how to test/load Ladle from the local folder (as yarn add "/path-to-folder" does not work as I thought).

I guess you could add it just to https://github.com/tajo/ladle/blob/master/packages/ladle/lib/cli/vite-base.js and few other places that use ladleConfig.

// https://theroadtoenterprise.com/blog/how-to-set-up-path-resolving-in-vite
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  plugins: [],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@plugins': path.resolve(__dirname, './src/plugins'),
    },
  },
});
// https://stackoverflow.com/questions/68217795/vite-resolve-alias-how-to-resolve-paths
// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: [
      { find: '@', replacement: path.resolve(__dirname, './src') },
      { find: '@plugins', replacement: path.resolve(__dirname, './src/plugins') },
    ]
  }
});

@emotion/react `css` feature is not working due to missing `jsxImportSource`

Is your feature request related to a problem? Please describe.
For @emotion/react to work with the css={css({...style})} feature it is needed that jsxImportSource: "@emotion/react" is set as well as the babelPlugins: ["@emotion/babel-plugin"]

Currently you can not set the jsxImportSource in the config because using @vite/react is not allowed to be used twice.

Describe the solution you'd like
Expose jsxImportSourceto be set in the config.mjs.
e.g.:

const config = {
  babelPlugins: ["@emotion/babel-plugin"],
  jsxImportSource: "@emotion/react",
};

export default config;
patch-package implementation diff
diff --git a/node_modules/@ladle/react/lib/cli/vite-base.js b/node_modules/@ladle/react/lib/cli/vite-base.js
index 1f8c430..706066d 100644
--- a/node_modules/@ladle/react/lib/cli/vite-base.js
+++ b/node_modules/@ladle/react/lib/cli/vite-base.js
@@ -110,6 +110,7 @@ const getBaseViteConfig = async (ladleConfig, configFolder, viteConfig) => {
       ladlePlugin(ladleConfig, configFolder),
       //@ts-ignore
       react({
+        jsxImportSource: ladleConfig.jsxImportSource,
         babel: {
           parserOpts: ladleConfig.babelParserOpts,
           presets: ladleConfig.babelPresets,

If you like I can submit a PR.

Ladle breaks when next/image component is imported

I have stories for simple components that work fine. But once I click on a story for a component that uses the next/image component, nothing renders and ladle is broken until reload. Once I clock on a story with next/image the following error occurs in the console:

react.development.js:1309 Uncaught ReferenceError: process is not defined
    at node_modules/next/dist/client/normalize-trailing-slash.js (normalize-trailing-slash.ts:12:43)
    at __require (chunk-MYIE5J7A.js?v=88870cd9:25:50)
    at node_modules/next/dist/shared/lib/router/router.js (router.ts:13:50)
    at __require (chunk-MYIE5J7A.js?v=88870cd9:25:50)
    at node_modules/next/dist/client/link.js (link.tsx:11:37)
    at __require (chunk-MYIE5J7A.js?v=88870cd9:25:50)
    at node_modules/next/link.js (link.js:1:18)
    at __require (chunk-MYIE5J7A.js?v=88870cd9:25:50)
    at dep:next_link:1:16
node_modules/next/dist/client/normalize-trailing-slash.js @ normalize-trailing-slash.ts:12
__require @ chunk-MYIE5J7A.js?v=88870cd9:25
node_modules/next/dist/shared/lib/router/router.js @ router.ts:13
__require @ chunk-MYIE5J7A.js?v=88870cd9:25
node_modules/next/dist/client/link.js @ link.tsx:11
__require @ chunk-MYIE5J7A.js?v=88870cd9:25
node_modules/next/link.js @ link.js:1
__require @ chunk-MYIE5J7A.js?v=88870cd9:25
(anonymous) @ dep:next_link:1
react_devtools_backend.js:3973 The above error occurred in one of your React components:

    at Lazy
    at Provider (http://localhost:61000/generated/generated-list.tsx:114:28)
    at Suspense
    at ErrorBoundary (http://localhost:61000/src/error-boundary.tsx:4:5)
    at Story (http://localhost:61000/src/story.tsx:11:18)
    at main
    at App (http://localhost:61000/src/app.tsx:24:41)

React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

Here is a screenshot for better readability:

Screen Shot 2022-04-14 at 17 43 33

Typecasting of the default export breaks the parsing of title/args and doesn't load the story

As claimed in the blog post (btw, really excited to try ladle), I tried to use ladle as a drop-in replacement, without success.

First problem is that we use a mono repo with some packages under <root>/packages, but ladle assumes stories will be under <root>/src, so first things first: configure where the stories are. Easily found the documentation, changed that accordingly, but when starting I got an error that no stories were found, still indicating that the default stories glob (src/**/*.stories.{js,jsx,ts,tsx}).

Starting with DEBUG=ladle* revealed the real problem:

  ladle:vite Parsing packages/components/src/Alert/Alert.stories.tsx +2ms
  ladle:vite Error when generating the list: +588ms
  ladle:vite Error: Can't parse the default title and meta of packages/components/src/Alert/Alert.stories.tsx. Meta must be serializable and title a string literal.
  ladle:vite     at getDefaultExport (file:///home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/lib/cli/vite-plugin/parse/get-default-export.js:32:11)
  ladle:vite     at NodePath._call (/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/@babel/traverse/lib/path/context.js:53:20)
(...)

So, there is something wrong with my default exports, it seems. This are the first few lines of that story:

import React from "react";  
import { Story, Meta } from "@storybook/react";    
import { action } from "@storybook/addon-actions";                                     
                    
import { SeverityOptions } from "@xxx/types";     
import { Alert } from "@xxx/components";                                     
                    
export default {     
  component: Alert,     
  title: "Components/Core/Alert",     
  argTypes: {       
    severity: {     
      options: Object.values(SeverityOptions),     
      control: { type: "select" },     
    },              
  },                
} as Meta;          
                    
const Template: Story = (args) => {  
  const [open, setOpen] = React.useState(true);  
  return (          
    <Alert {...args} open={open} onClose={() => setOpen(false)}>  
      {args.children}  
    </Alert>        
  );                
};                  
                    
export const Default = Template.bind({});  
Default.args = {    
  children: "Este รฉ um alerta",  
};  

Commenting out the default exports gets me a little bit further. (by now, I am isolating the stories and using just this one, since all our stories have default exports).

I now see a loading spinner with all my stories on the right side menu. Debug logs don't complain about default exports. Some vite messages, but then when it tries to reload, boom.

6:33:22 AM [vite] new dependencies found: @material-ui/icons, @material-ui/core/TextField, date-fns/locale, @material-ui/lab, @material-ui/icons/NavigateNextOutlined, formik, updating...
6:33:25 AM [vite] โœจ dependencies updated, delaying reload as new dependencies have been found...
6:33:25 AM [vite] new dependencies found: @testing-library/user-event, @sentry/browser, fast-xml-parser, currency.js, updating...
/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/vite/dist/node/chunks/dep-6ae84d6b.js:51603
    const err = new Error(`There is a new version of the pre-bundle for "${id}", ` +
                ^

Error: There is a new version of the pre-bundle for "/home/romaia/dev/stoq/stoq-pdv/node_modules/.vite/deps/@material-ui_icons.js?v=8f0250ca", a page reload is going to ask for it.
    at throwOutdatedRequest (/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/vite/dist/node/chunks/dep-6ae84d6b.js:51603:17)
    at Context.load (/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/vite/dist/node/chunks/dep-6ae84d6b.js:51557:29)
    at Object.load (/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/vite/dist/node/chunks/dep-6ae84d6b.js:35159:50)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async doTransform (/home/romaia/dev/stoq/stoq-pdv/node_modules/@ladle/react/node_modules/vite/dist/node/chunks/dep-6ae84d6b.js:51433:24) {
  code: 'ERR_OUTDATED_OPTIMIZED_DEP'
}

Important disclaimer: This project does not use vite (yet). We are currently using webpack. It was not clear from the documentation if this would be a problem or not.

Vite config.plugins and config.css support

Hello! This is a very exciting project, I am trying to integrate it into our project, the problem now is that we rely on some Vite plugins to render components properly, is there any plan to support more vite options such as config.plugins and config.css? Or provide a generic method to customize all vite options? Thanks!

const viteConfig = await getBaseViteConfig(config, configFolder, {
mode: "development",
define: config.serve.define,
server: {
port: config.serve.port,
open: config.serve.open,
fs: {
strict: false,
},
middlewareMode: "html",
},
});

Overwriting glob, no stories found

HI there,

I wanted to tryout Ladle, although my components are not found in the src folder. Therefore I wanted to overwrite the default glob, but unfortunately Ladle doesn't seem to pickup the change. Happening both via CLI as well as via the config file. Any thought?

Screenshot 2022-03-23 at 10 08 22

componentsRelativePath broken on Windows?

Steps to reproduce:

  • Add basic Ladle setup: @ladle/react, react, react-dom.
  • Create the simplest story under src/button.jsx.
  • Add global provider under .ladle/components.js:
    import React from 'react';
    
    export const Provider = ({ children, globalState }) => (
      <>
        <h1>Theme: {globalState.theme}</h1>
        {children}
      </>
    );
  • Try to run the server.
[plugin:vite:import-analysis] Failed to resolve import "............ladle.ladlecomponents.js" from "..\node_modules\@ladle\react\lib\app\generated\generated-list.tsx". Does the file exist?
C:[/packages/node_modules/@ladle/react/lib/app/generated/generated-list.tsx:17:0]()
6  |  export let stories = { "button--default": { component: button$$default } };
7  |  export let config = {};
8  |  import { Provider as CustomProvider } from "............ladle.ladlecomponents.js";
   |                                              ^
9  |  export const Provider = CustomProvider;
10 |  if (import.meta.hot) {
    at formatError ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.js:36237:46]())
    at TransformContext.error ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.js:36233:19]())
    at normalizeUrl ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.js:56981:26]())
    at processTicksAndRejections (node:internal[/process/task_queues:96:5]())
    at async TransformContext.transform ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.js:57130:57]())
    at async Object.transform ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.js:36474:30]())
    at async doTransform ([packages\node_modules\vite\dist\node\chunks\dep-a7fb482c.]()

This is the code that generates the relative path:

const componentsRelativePath = path.relative(
path.join(__dirname, "../../../app/src"),
path.join(
configFolder,
componentsExists ? "components.tsx" : "components.js",
),
);

However, this results in

'..\\..\\..\\..\\..\\..\\ladle\\.ladle\\components.js'

so this looks okay. However, the entire function returns

import {Provider as CustomProvider} from '..\..\..\..\..\..\ladle\.ladle\components.js';

! The double backslashes are gone.

So I monkey patched the code:

+      function escapePath(pathString) {
+        return pathString.replace(/\\/g, "\\\\");
+      }
-      return `import {Provider as CustomProvider} from '${componentsRelativePath}';\nexport const Provider = CustomProvider;\n`;
+      return `import {Provider as CustomProvider} from '${escapePath(componentsRelativePath)}';\nexport const Provider = CustomProvider;\n`;

and boom, it worked!

js. and .ts files must import React explicitly: (Uncaught ReferenceError: React is not defined)

I've tried the example code and I'm getting an empty story:

Screen Shot 2022-03-16 at 11 00 36

Upon inspecting my console, there's this error:

Uncaught ReferenceError: React is not defined
...

I'm running it with npx ladle serve, and both that and npx ladle build + a local webserver will render the frame properly, receive the correct data when clicking on the component (as seen in the example), but not render the component on the left. So I changed this code:

export default {
  title: "Level / Sub level",
};
export const Button = () => <button>My Button</button>;

Into this one, and now it works, note the first line change:

import React from "react";

export default {
  title: "Level / Sub level",
};
export const Button = () => <button>My Button</button>;

My react version is 17.0.2 (same as react-dom) since 18.x is not yet official. Should we add the import React from "react"; to the docs? Or maybe I'm missing some config that would make it not needed? The only different thing I'm doing is running it with npx ladle serve instead of yarn ladle serve otherwise.

Name Customization: Not possible to use logic (concat string, helper functions etc.)

Is your feature request related to a problem? Please describe.
I would like to streamline story namespaces using the Name Customization feature. However, it does not seem to be possible to use any logic exporting a story title.

Doing so a blank page is shown an I get this error in the browser console:

Uncaught SyntaxError: The requested module '/generated/generated-list.tsx' does not provide an export named 'storySource' (at source.tsx:6:1)

Working Example:

export default {
    title: 'Core / Button',
};

Broken Exmaples:

export default {
    title: 'Core' + ' / ' + 'Button',
};

or 

export default {
    title: `${'Core'} / ${'Button'}`,
};

My usecase:

// utils.ts
const getStoryName = (namespace: 'Core' | 'Features', componentName: string) =>
    `${namespace} / ${componentName}`;

// button.story.ts
export default {
    title: getStoryName('Core', 'Button'),
};

ladle example throws error : Failed to resolve import

Laddel example throws error
When I run the example script and start ladle, it throws an error.

Steps to reproduce the behavior

mkdir my-ladle
cd my-ladle
yarn init --yes
yarn add @ladle/react react react-dom
mkdir src
echo "export const World = () => <p>Hey</p>;" > src/hello.stories.tsx
yarn ladle serve

Error:

[plugin:vite:import-analysis] Failed to resolve import "..
ode_modules@ladle
eactlibappsrc/story-hmr" from "src\hello.stories.tsx". Does the file exist?
C:/git/my-ladle/src/hello.stories.tsx:33:31
31 |  }
32 |  
33 |  import { storyUpdated } from "..\node_modules\@ladle\react\lib\app\src/story-hmr";
   |                                ^
34 |  if (import.meta.hot) {
35 |            import.meta.hot.accept(() => {

Expected behavior
A clear and concise description of what you expected to happen.

Desktop:

  • OS: win 10
  • Browser: chrome
  • react 18.1.0
  • ladle: 0.15.0

`ladle serve` error: "Failed to resolve force included dependency: react/jsx-dev-runtime"

Thanks for making this tool! Excited to see the performance improvements the blog post touts.

I tried testing it out as a drop-in for Storybook in one of our projects and see the following error message. Any idea what might be going wrong? The closest relevant issue I see is this one from last November (which you happened to comment on) but theoretically that is now fixed so not sure if it's related.

โฏ yarn add --dev @ladle/react
...
โฏ DEBUG=ladle* yarn ladle serve
yarn run v1.22.17
$ /Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/.bin/ladle serve
  ladle:cli Starting serve command +0ms
  ladle:cli CLI theme: undefined +1ms
  ladle:cli CLI stories: undefined +0ms
  ladle:cli CLI port: undefined +0ms
  ladle:cli CLI open: undefined +0ms
  ladle:cli No custom config found. +4ms
  ladle:cli Final config:
  ladle:cli {
  ladle:cli   "stories": "src/**/*.stories.{js,jsx,ts,tsx}",
  ladle:cli   "root": "/Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook",
  ladle:cli   "defaultStory": "",
  ladle:cli   "babelPresets": [],
  ladle:cli   "babelPlugins": [],
  ladle:cli   "define": {},
  ladle:cli   "envPrefix": "VITE_",
  ladle:cli   "resolve": {
  ladle:cli     "alias": {}
  ladle:cli   },
  ladle:cli   "optimizeDeps": {
  ladle:cli     "include": []
  ladle:cli   },
  ladle:cli   "addons": {
  ladle:cli     "control": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": {}
  ladle:cli     },
  ladle:cli     "theme": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": "light"
  ladle:cli     },
  ladle:cli     "mode": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": "full"
  ladle:cli     },
  ladle:cli     "rtl": {
  ladle:cli       "enabled": true,
  ladle:cli       "defaultState": false
  ladle:cli     },
  ladle:cli     "ladle": {
  ladle:cli       "enabled": true
  ladle:cli     }
  ladle:cli   },
  ladle:cli   "serve": {
  ladle:cli     "open": "**Default**",
  ladle:cli     "port": 61000,
  ladle:cli     "define": {}
  ladle:cli   },
  ladle:cli   "build": {
  ladle:cli     "out": "build",
  ladle:cli     "sourcemap": false,
  ladle:cli     "baseUrl": "/",
  ladle:cli     "define": {}
  ladle:cli   }
  ladle:cli } +0ms
  ladle:cli Port set to: 61000 +3ms
Error: Failed to resolve force included dependency: react/jsx-dev-runtime
    at addManuallyIncludedOptimizeDeps (/Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/vite/dist/node/chunks/dep-19d0f55f.js:35966:27)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async createOptimizeDepsRun (/Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/vite/dist/node/chunks/dep-19d0f55f.js:35721:13)
    at async runOptimize (/Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/vite/dist/node/chunks/dep-19d0f55f.js:54927:30)
    at async createServer (/Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/vite/dist/node/chunks/dep-19d0f55f.js:54961:9)
    at async bundler (file:///Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/@ladle/react/lib/cli/vite-dev.js:38:18)
    at async Command.serve (file:///Users/nathan/work/go/src/github.com/opendoor-labs/code/js/packages/bricks-storybook/node_modules/@ladle/react/lib/cli/serve.js:41:3)

Base vite-config overrides the default esbuild loader for ts/tsx

Hello! I noticed that the base vite-config overrides the default esbuild loader for ts/tsx.

https://github.com/tajo/ladle/blob/master/packages/ladle/lib/cli/vite-base.js#L73

This causes errors in .ts files that are using .ts-specific syntax, for example:

The character ">" is not valid inside a JSX element
81 |    value: TValue,
82 |    options: UseCellOptions<TValue>
83 |  ): SomeResult<TValue> => {
   |                         ^

Is there a specific reason for this or can it be removed?

Testing aspect of Ladle

Ladle is an environment to develop, test and share your React components faster.

My question is how to test the components?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.