Giter VIP home page Giter VIP logo

vitedge's Introduction

Vitedge

Vite Edge Side Rendering (ESR) framework for Vue and React, or bring your own view library.

What's ESR? Think of SSR (Server Side Rendering) in CDN nodes instead of actual servers. This is possible today thanks to Cloudflare Workers (and maybe some other platforms in the near future).

Vitedge is just a Vite app ™ that prerenders the first view in an edge worker and runs the rest as an SPA. That means it will lead to good SEO while keeping the snappy routing and DX of an SPA.

It can replace static site generators in some situations since it builds on the fly and caches at the edge. Therefore, instead of getting a static index.html from the CDN, the CDN itself will create it on the fly or provide it from cache if it was already accessed (with configurable cache age + stale-while-revalidate).

Even though running it at the edge is ideal, it is actually compatible with any Node environment such as Vercel or Netlify.

See live demo, and Vue or React starter templates. If you want to bring your own view library, have a look at the Vanilla JS example as a guide.

Features

  • ⚡ Ultrafast development and HMR powered by Vite and ES Modules.
  • ⚔️ Renders and caches at the edge for maximum performance in production. Cache is configurable.
  • 💁‍♂️ Each page gets its server data as props by default but can be set in a store instead.
  • 🔽 HTTP/2 server push for your assets to speed up the loading time without waterfall requests.
  • 🧱 Automatically creates endpoints for your API based on filesystem routes.

Docs & Community

See docs.

To talk about Vitedge, join ViteLand Discord and check #vitedge channel or use GitHub's Discussions.

Starters

Roadmap

  • Support TypeScript projects.
  • Custom Vite dev-server that serves API/Props during development.
  • Docs website.
  • Extract CF worker boilerplate as utilities.
  • Cache props/html in worker and make it configurable.
  • i18n compatible.
  • Starter template.
  • Auth utilities/guide (passing JWT in requests as cookies).
  • Compatibility with Node runtime for other providers (Vercel/Netlify...).
  • Add example using Vercel's edge cache.
  • Detect imported files in HTML and push them with HTTP/2.
  • Add an SSR mode for local development (web worker?).
  • Support GraphQL, sitemap and other dynamic endpoints.
  • React compatibility.
  • Provide React starter template.
  • Support Vite 2.
  • HMR for API side.
  • Page props HMR in browser on file save.
  • Preload assets using Vite's manifest.
  • Support self-requests to API endpoints during SSR.
  • Support parameters and wildcards in API file routes (api/path/[param].js).
  • Stale-while-revalidate cache for pages.
  • CORS defaults.
  • Throw errors from API/Props endpoints.
  • Redirects with 3xx HTTP codes.
  • Mockup KV and cache in development.
  • Mockup DO in development.
  • Rewrite in TypeScript.
  • Guide to bring your own view framework.
  • Preview mode to simulate Worker environment in development.
  • Support Wrangler v2.
  • Deploy to fullstack Cloudflare Pages.
  • Streaming mode.
  • Support React 18

Contributing

See contributing guide.

vitedge's People

Contributors

artisin avatar benmccann avatar frandiox avatar hrgui avatar huygn avatar m3hari avatar odai-alali avatar oleghalin avatar the-amateur-dev avatar yayvery 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  avatar  avatar  avatar  avatar  avatar  avatar

vitedge's Issues

Hook into fetchPageProps() to display loading component?

Is it possible to hook into fetchPageProps() to change the state while fetching the props on client side navigation? I would like to display a loading component in case it takes a bit too long.

I tried router.beforeEach() but its triggered after fetching props is finished.

API parameters

Hi, thank you for this amazing project. I read in the docs that API parameters are not currently supported out of the box and must be done manually. Is there an example of how to do it manually? If not would it be possible to provide an example or guidance on how I could do that please?

Trying to get the example to work.

Hi, @frandiox, it's me again!

I'm doing my best to figure this out, and please pardon my complete ignorance in this area. I'm very grateful for your work and plan on joining the cause as soon as I can come to a full comprehension. I'm really excited to show feathers-vuex users how to use it for a stellar setup with FeathersJS. Since you said you are open to suggestions and support with the experience of getting up and running, I have a couple of thoughts. One would be to move the vitedge vue example into a its own github repo so it can be one-click cloned to create a new project. That would make it just one click to start a new, working project. For clarity, here's a screenshot of the Vitesse repo that shows the "Use this template" button:

Screen Shot 2021-02-21 at 6 35 55 PM

Having a template repo would make it easier to get up and running after breaking changes.

I attempted to create this repo myself by cloning the vitedge project and moving the vue example to the top level. Please check my repo, here: https://github.com/marshallswain/vitedge.

My goal is to make it possible to clone and just be able to run npm run dev and have it just work. In this case, I did update the packages to the latest patch release, which maybe is causing the problem that I'm running into. This is the error message I'm getting:

Screen Shot 2021-02-21 at 6 40 39 PM

Screen Shot 2021-02-21 at 6 30 02 PM

It's not clear to me why this is occurring. Any support in getting the example to run would be especially appreciated. Thank you!

Add better support for slugs?

To make the slug work this is what I have done so far.
Added
const pages = import.meta.glob('./pages/**/*.jsx') in routes.js

Then added a filename as src/pages/projects/:name.jsx

This is working. What would be the best practice to do this?
With this approach, if I need to add a not-found page something like this, then I have to add the page like src/pages/projects/*.jsx. What do you think?

First load for the simple Hello world sample is about 1,59MB

Hello,

I don't known if this is expected, but just following the documentation sample for the Hello World + counter, and on the first load there is 1,59MB to download, it's a lot for a so small part of code, and impact SEO performances.

Do you known what cause theses files sizes ?
Photo of assets files sizes

Props redirect issue

From Discord via @subhendukundu:

Lets say I have these routes.

functions/props/docs/[slug].js
and
functions/props/docs.js

and in /docs.js

export default {
    handler({ params = {}, query = {} }) {
        return {
            status: 302, // Any 3xx is accepted for a redirect
            headers: {
                location: "/docs/getting-started",
            },
        };
    },
    options: {
        cache: {
            api: 90,
            html: 90,
        },
    },
};

And this is affecting in docs/[slug] paths as well.

yes. I have two routes.

  1. /docs
  2. docs/[slug]
    And I am trying to redirect user from /docs to /docs/getting-started

git clone -b props-route https://github.com/subhendukundu/vitedge-react-template.git

Solid.js support

Hi there, this looks like a great project. I'm investigating how much effort it would be to support Solid.js. Solid SSR is about 2x Marko according to tests against isomorphic-ui-benchmarks, so I think it make a fantastic candidate for Worker edge serving.

Is this as straightforward as replacing some Vite config options? If you provide some guidance I can likely handle a PR for you to entertain.

TS types for pages

Currently, (as far as I can tell), pages do not receive typed props

e.g. examples/react/src/pages/Home.jsx
Screen Shot 2021-08-15 at 7 28 03 PM

Ideally, a page would get the types automatically. Worst case, a page could import a type (from functions/{props,api} or elsewhere) but I believe it's really important the pages receive typed props.

Perhaps this is all under the "Rewrite in TypeScript" TODO, but I wanted to highlight this specific case and ask for it to be prioritized.

Optional chaining breaks when deploying to Cloudflare

Using TypeScript's optional chaining syntax causes errors when using Wrangler to deploy the site to Cloudflare

$ wrangler dev
👀  ../dist/functions.js 84:29
Module parse failed: Unexpected token (84:29)
You may need an appropriate loader to handle this file type.
|     return new URLSearchParams();
|   }
>   const queryString = window?.location?.search;
|   return new URLSearchParams(queryString);
| };
 @ ../node_modules/vitedge/worker/utils.js 1:0-39 6:34-37
 @ ../node_modules/vitedge/worker/index.js
 @ ./index.js
Error: webpack returned an error. Try configuring `entry` in your webpack config relative to the current working directory, or setting `context = __dirname` in your webpack config.
error Command failed with exit code 1.

Multiple errors when trying to follow documentation

Hello,

This project seems awesome ! I'm trying to test it, so I'm following documentation steps but I can't make it works:

 ~/d/f/serverless  LANG=C vitedge dev --ssr                                                                                                                     1041ms  mar. 27 avril 2021 12:11:47
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
(node:27981) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
Pre-bundling dependencies:
  vitedge
  vue
  vitedge/vue/entry-client
  vitedge/vue/entry-server
  vite-ssr/vue/entry-client
  (...and 1 more)
(this will be run only when your dependencies or config have changed)
 > node_modules/vite-ssr/vue/entry-client.js:2:47: error: Could not resolve "vue-router" (mark it as external to exclude it from the bundle)
    2 │ import { createRouter, createWebHistory } from 'vue-router';
      ╵                                                ~~~~~~~~~~~~

 > node_modules/vitedge/vue/entry-server.js:3:27: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
    3 │ import { createHead } from '@vueuse/head'
      ╵                            ~~~~~~~~~~~~~~

 > node_modules/vitedge/vue/entry-client.js:4:27: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
    4 │ import { createHead } from '@vueuse/head'
      ╵                            ~~~~~~~~~~~~~~

 > node_modules/vitedge/vue/utils.js:1:50: error: Could not resolve "vue-router" (mark it as external to exclude it from the bundle)
    1 │ import { createRouter, createMemoryHistory } from 'vue-router'
      ╵                                                   ~~~~~~~~~~~~

 > node_modules/vite-ssr/vue/entry-server.js:3:50: error: Could not resolve "vue-router" (mark it as external to exclude it from the bundle)
    3 │ import { createRouter, createMemoryHistory } from 'vue-router';
      ╵                                                   ~~~~~~~~~~~~

 > node_modules/vite-ssr/vue/entry-server.js:8:35: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
    8 │ import { renderHeadToString } from '@vueuse/head';
      ╵                                    ~~~~~~~~~~~~~~

/home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:1224
  let error = new Error(`${text}${summary}`);
              ^

Error: Build failed with 6 errors:
node_modules/vite-ssr/vue/entry-client.js:2:47: error: Could not resolve "vue-router" (mark it as external to exclude it from the bundle)
node_modules/vite-ssr/vue/entry-server.js:3:50: error: Could not resolve "vue-router" (mark it as external to exclude it from the bundle)
node_modules/vite-ssr/vue/entry-server.js:8:35: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
node_modules/vitedge/vue/entry-client.js:4:27: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
node_modules/vitedge/vue/entry-server.js:3:27: error: Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)
...
    at failureErrorWithLog (/home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:1224:15)
    at buildResponseToResult (/home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:936:32)
    at /home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:1035:20
    at /home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:568:9
    at handleIncomingPacket (/home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:657:9)
    at Socket.readFromStdout (/home/guillaume/dev/serverless/node_modules/esbuild/lib/main.js:535:7)
    at Socket.emit (node:events:378:20)
    at addChunk (node:internal/streams/readable:313:12)
    at readableAddChunk (node:internal/streams/readable:288:9)
    at Socket.Readable.push (node:internal/streams/readable:227:10) {
  errors: [
    {
      detail: undefined,
      location: {
        column: 47,
        file: 'node_modules/vite-ssr/vue/entry-client.js',
        length: 12,
        line: 2,
        lineText: "import { createRouter, createWebHistory } from 'vue-router';",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "vue-router" (mark it as external to exclude it from the bundle)'
    },
    {
      detail: undefined,
      location: {
        column: 50,
        file: 'node_modules/vite-ssr/vue/entry-server.js',
        length: 12,
        line: 3,
        lineText: "import { createRouter, createMemoryHistory } from 'vue-router';",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "vue-router" (mark it as external to exclude it from the bundle)'
    },
    {
      detail: undefined,
      location: {
        column: 35,
        file: 'node_modules/vite-ssr/vue/entry-server.js',
        length: 14,
        line: 8,
        lineText: "import { renderHeadToString } from '@vueuse/head';",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)'
    },
    {
      detail: undefined,
      location: {
        column: 27,
        file: 'node_modules/vitedge/vue/entry-client.js',
        length: 14,
        line: 4,
        lineText: "import { createHead } from '@vueuse/head'",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)'
    },
    {
      detail: undefined,
      location: {
        column: 27,
        file: 'node_modules/vitedge/vue/entry-server.js',
        length: 14,
        line: 3,
        lineText: "import { createHead } from '@vueuse/head'",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "@vueuse/head" (mark it as external to exclude it from the bundle)'
    },
    {
      detail: undefined,
      location: {
        column: 50,
        file: 'node_modules/vitedge/vue/utils.js',
        length: 12,
        line: 1,
        lineText: "import { createRouter, createMemoryHistory } from 'vue-router'",
        namespace: ''
      },
      notes: [],
      text: 'Could not resolve "vue-router" (mark it as external to exclude it from the bundle)'
    }
  ],
  warnings: [
    {
      detail: undefined,
      location: {
        column: 63,
        file: 'node_modules/@vue/server-renderer/dist/server-renderer.cjs.js',
        length: 7,
        line: 99,
        lineText: "    return (compileCache[template] = Function('require', code)(require));",
        namespace: ''
      },
      notes: [],
      text: 'Indirect calls to "require" will not be bundled (surround with a try/catch to silence this warning)'
    }
  ]
}

Here is the steps I've done:

~/d   npm init @vitejs/app serverless --template vue                                                                                                    425ms  mar. 27 avril 2021 11:50:13
✔ Select a framework: · vue
✔ Select a variant: · JavaScript

Scaffolding project in /home/guillaume/dev/serverless...

Done. Now run:

  cd serverless
  npm install
  npm run dev

 ~/d  cd serverless/                                                                                                                                     5.3s  mar. 27 avril 2021 11:50:42
 ~/d/serverless  npm install                                                                                                                                           mar. 27 avril 2021 11:50:44

added 55 packages, and audited 56 packages in 5s

3 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
 ~/d/serverless  npm i vitedge                                                                                                                                  5.1s  mar. 27 avril 2021 11:51:20
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: '[email protected]',
npm WARN EBADENGINE   required: { node: 'node >= 14' },
npm WARN EBADENGINE   current: { node: 'v15.11.0', npm: '7.6.0' }
npm WARN EBADENGINE }

added 74 packages, and audited 130 packages in 9s

8 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
 ~/d/serverless  mkdir -p functions/props                                                                                                                       9.7s  mar. 27 avril 2021 11:51:54
 ~/d/serverless  mkdir -p functions/api                 

I've updated files:
vite.config.js

import { defineConfig } from 'vite'
import vitedgePlugin from 'vitedge/plugin.js'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vitedgePlugin(), vue()]
})

/src/main.js

import './index.css'
import App from './App.vue' // App.jsx
import routes from './routes'
import vitedge from 'vitedge'

export default vitedge(
  App,
  { routes },
  ({ app, router, isClient, initialState }) => {
    // Custom setup hook.
    // E.g. set initialState in a store, install plugins, etc.
  }
)

( it's not explained in the documentation, but I've took a part of a routes.js from the samples on your git )
/src/routes.js

export default [
    {
      path: '/',
      component: () => import('./pages/home.vue'),
      name: 'home',
      meta: {
        propsGetter: 'default',
      },
    },
   {
      path: '/:catchAll(.*)',
      name: 'not-found',
      component: () => import('./pages/404.vue'),
      meta: {
        propsGetter: false,
      },
    },
  ]

And created 2 simples /pages/ .vue files:

/pages/404.vue

<template>
  <div>
    <h1>Not found</h1>
    <a href="/">Home</a>
  </div>
</template>

/pages/home.vue

<template>
  <div>
    <h1>Homepage</h1>
    <a href="/">Home</a>
  </div>
</template>

BTW if I start the server without the --ssr mode, I can go on the localhost:3000 but with an empty white page and my browser try 2 requests for index.html and favicon with a 404 error.

Any idea ?
Thanks

Svelte support?

Hey! Svelte support would be amazing. Especially since Sveltekit is built around Vite 🙏. Any plans for it?

unable to vitedge build

Repo: https://github.com/hrgui/pokedex-vitedge

warning package.json: No license field
$ rm -rf dist && vitedge build
(node:10381) UnhandledPromiseRejectionWarning: Error: Transform failed with 1 error:
/Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:239:12: error: Invalid option in transform() call: "jsx"
    at failureErrorWithLog (/Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:1443:15)
    at /Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:1288:20
    at /Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:606:9
    at handleIncomingPacket (/Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:703:9)
    at Socket.readFromStdout (/Users/hrgui/projects/pokedex-viteedge/node_modules/esbuild/lib/main.js:573:7)
    at Socket.emit (events.js:315:20)
    at addChunk (_stream_readable.js:309:12)
    at readableAddChunk (_stream_readable.js:284:9)
    at Socket.Readable.push (_stream_readable.js:223:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:10381) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:10381) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
✨  Done in 0.71s.

I am also getting this issue on this repo: https://github.com/subhendukundu/vitedge-react-template

yarn run v1.22.10
warning package.json: No license field
$ rm -rf dist && vitedge build
building for production...
✓ 62 modules transformed.
dist/client/assets/logo.ecc203fb.svg    2.61kb
dist/client/index.html                  0.46kb
dist/client/manifest.json               0.78kb
dist/client/ssr-manifest.json           7.19kb
dist/client/assets/About.3a223546.js    0.19kb / brotli: 0.12kb
dist/client/assets/index.10d42577.js    2.69kb / brotli: 1.13kb
dist/client/assets/Home.b57b4c20.js     0.41kb / brotli: 0.21kb
dist/client/assets/index.e9904e82.css   0.46kb / brotli: 0.21kb
dist/client/assets/vendor.9557a7b8.js   166.09kb / brotli: 47.07kb
building SSR bundle for production...
✓ 18 modules transformed.
dist/ssr/main.js   24.62kb
(node:10830) UnhandledPromiseRejectionWarning: Error: Transform failed with 1 error:
/Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:239:12: error: Invalid option in transform() call: "jsx"
    at failureErrorWithLog (/Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:1443:15)
    at /Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:1288:20
    at /Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:606:9
    at handleIncomingPacket (/Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:703:9)
    at Socket.readFromStdout (/Users/hrgui/projects/vitedge-react-template/node_modules/esbuild/lib/main.js:573:7)
    at Socket.emit (events.js:315:20)
    at addChunk (_stream_readable.js:309:12)
    at readableAddChunk (_stream_readable.js:284:9)
    at Socket.Readable.push (_stream_readable.js:223:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:10830) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:10830) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
✨  Done in 5.37s.

Skip props request for static routes

Right now, a props handler file needs to export a handler function. It should be possible to omit this handler and only export cache options. In this scenario, the route can skip the props request in SPA mode while still having access to cache options.

Alternatives:

  • Keep functions/props/routeName.js naming and check the content to see whether handler funciton is exported or not.
  • Use functions/props/routeName.static.js naming.
  • Use functions/props/routeName.json naming. -- It seems nice but it wouldn't have type auto completion.

Vite-Plugins Incompatible

When using vite plugins like vite-plugin-pages or vite-plugin-components I get ERR_UNSUPPORTED_DIR_IMPORT or is not a Function. Any ideas how to get that working? Without vite plugins the DX for me is not there sadly.

Bug report: error during dev

I'm running into the following error while loading the dev server:
Screen Shot 2021-02-14 at 1 53 11 PM

The dev server actually starts without any errors, as shown in this screenshot:
Screen Shot 2021-02-14 at 1 52 32 PM

This same error is occurring with both the example folder in this repo as well as a separate project that I tried setting up manually. Here is a link to the repo: https://github.com/marshallswain/my-vitedge-project

After cloning the repo, just run npm i then npm run dev, then open the dev server in the browser.

Custom headers are missing in local env

const res = await fetch(url, {
    method: "POST",
    headers: new Headers({ "Content-Type": "application/json", token }),
    credentials: "same-origin",
    body: JSON.stringify(data),
});

I am expecting the token in our API like so

async handler({ request }) {
        if (request.method !== "POST") {
            throw new BadRequestError("Method not supported!");
        }
        const { price, metadata = {} } = await request.json();
        const token = request.headers.get("token");
        console.log(token); // undefined here only in local -----------

But it works when deployed.
Any idea why?

Deploy to Static File Host

Hello again,

I'm using Vitedge to build a Vue app on Cloudflare. However, I'd also like to deploy a static version to a regular file host. I see there are two folders in dist after running npm run build: client and ssr. But I see no HTML files in there. Is there any way I can generate a static SPA using Vitedge?

custom status code and staus text in functions?

For example if I need to perform an api call before returning data and based on the api call I need to send a response. How would you do that?

export default ({
  handler({ request }) {
     const postCall = await fetch(API_ROOT_URL, {
      method: "post",
     });
     const data = await postCall.json();
     return {
        data
    }
  },
  options: {
     status: 400, // but only if the api  await fetch(API_ROOT_URL) fails otherwise 200
    cache: {
      api: 90,
      html: 90,
    },
  },
})

Add support for Akamai EdgeWorkers

It would be great to add support for Akamai EdgeWorkers. I can look into creating a PR, but I wanted to start with an issue to begin tracking it.

error on deloy

When I am running yarn deploy after yarn build, it yields error.

Built successfully, built project size is 757 KiB.
🌀  Using namespace for Workers Site "__cool-workers_sites_assets"
✨  Success
🌀  Uploading site files
Error: Something went wrong! Status: 400 Bad Request, Details {
  "result": null,
  "success": false,
  "errors": [
    {
      "code": 10021,
      "message": "Script startup timed out.\n"
    }
  ],
  "messages": []
}

error Command failed with exit code 1

Have you see this issue?
I was talking to Greg about this, he says it is an issue when too many things being initialized as globals

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request)
})

const doWork = async () => { /* ... */ }

const handleRequest = async request => {
  const results = await doWork()
  return new Response(JSON.stringify(results))
}

vs.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request)
})

const results = /* ... */

const handleRequest = async request => {
  return new Response(JSON.stringify(results))
}

The first example (good) does the work in the request/response phase, with just the function declaration in the global state.

The second example (bad) does the work in the global state.

Do you know how to fix this?

HTTP Status Codes

Hi all and thanks a lot for the great plugin! 👍

I'm trying to prevent soft 404's when working with vitedge. I already managed to return an HTTP 404 status code using this code in functions/props/NotFound.ts:

import type { EdgeProps } from 'vitedge'
import { NotFoundError } from 'vitedge/errors'

export default <EdgeProps> {
  async handler() {
    throw new NotFoundError('Not Found')
  }
}

I tried with some non-existing pages on my website and they show the correct Vue view. The calls to the vitedge API also return 404. However, the Vue page itself still return a 200 status code. Is it possible to reflect the status code of the API response also on the Vue page?

Some sub modules does got missing after build ( on production )

after build vitedge does not handle modules that where importer by others. Here is the error I get when try to use vitedge with node (default example)

While building this error occurs

preferring built-in module 'querystring' over local alternative at '/Users/somedrive/Downloads/gn/node_modules/querystring/index.js', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
 Cannot find package 'object-assign' imported from /Users/somedrive/Downloads/gn/dist/functions.js
code: 'ERR_MODULE_NOT_FOUND'

I am using a lib to connect to a database. When I move this same code to server/index.js (express) everything goes ok, but when I try to use into functions/api that error come out.

Here is how I call the lib

import ClickHouse from '@apla/clickhouse';
const ch = new ClickHouse({ host: 'localhost', port: 8123, user: 'default', password: 'justpass' });

The missing module is already installed by the main module (Clickhouse lib). It supposes to not ask to install it on our vitedge app.

JWT for local and worker both

JWT is working on local when I am using import jwt from "jsonwebtoken"; but not import jwt from "@tsndr/cloudflare-worker-jwt";. And in workers, it works the other way.
I understand because both use different environments. Is there was we can make it work in both? Based on process.env.NODE_ENV? Thoughts?

The Dev Server Won't Work for Vue

Steps to Replicate the Error

I simply followed the directions from the docs with a brand new project

$ yarn create @vitejs/app my-vue-app --template vue
$ cd my-vue-app
// added "type": "module", to my package.json
$ yarn add vitedge vue@3 @vue/server-renderer@3 vue-router@4 @vueuse/head

I then added vitedgePlugin to vite.config.js

// vite.config.js
import vue from '@vitejs/plugin-vue'
import vitedgePlugin from 'vitedge/plugin.js'

export default {
  plugins: [vitedgePlugin(), vue()],
}

and then I refactored my src/main.js to look like this:

// src/main.js
import vitedge from 'vitedge'
import App from './App.vue'
import routes from './routes'

export default vitedge(App, { routes }, () => {})

I also have tried it with initializing the vue-router stuff (defining a home route, creating a home page, adding to the App.vue) but the error is the same either way

I also created the directories functions/api/index.js and functions/props/index.js (but I still get the error when the functions directory is removed)

here's my package.json:

{
  "name": "my-vue-app",
  "version": "0.0.0",
  "scripts": {
    "dev": "vitedge dev --ssr",
    "dev:spa": "vitedge dev",
    "build": "vitedge build",
    "serve": "vite preview",
    "reset": "rm -rf node_modules && npx yarn install"
  },
  "dependencies": {
    "@vue/server-renderer": "3",
    "@vueuse/head": "^0.6.0",
    "vitedge": "^0.16.0",
    "vue": "3",
    "vue-router": "4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.3.0",
    "@vue/compiler-sfc": "^3.0.5",
    "vite": "^2.4.4"
  },
  "type": "module"
}

I'm running:
windows: 10 21H1
yarn 1.22.5
node: 14.17.5
npm: 7.21.0

Expected Behavior

when I run yarn vitedge dev or yarn vitedge dev --ssr, a dev server is spun up, and I'm able to preview my app locally

Current Behavior

if I try to run:

yarn vitedge dev
npx vitedge dev

I get:

(node:17780) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file:///C:/<my project directory>/node_modules/.bin/vite" 
    at Loader.getFormat (internal/modules/esm/loader.js:118:13)
    at async Loader.getModuleJob (internal/modules/esm/loader.js:243:20)
    at async Loader.import (internal/modules/esm/loader.js:177:17)
    at async Object.loadESM (internal/process/esm_loader.js:68:5) {
  code: 'ERR_INVALID_MODULE_SPECIFIER'
}

and when I try to run it in SSR mode, I get the same error but for vite-ssr instead

Does it support streaming html?

My use case requires rapid changing content that therefore can't be cached + also fast TTFB.

Is it possible to stream part of the html instead of sending all of it in one chunk, causing long TTFB?

Error [ERR_REQUIRE_ESM]: require() of ES Module /node_modules/vitedge/plugin.js not supported.

Hello, I am trying to run vitessedge-template, however when running npm run dev I get the following error:

/home/mihai/health-plus/@app/homepage/vite.config.ts:37
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                               ^

Error [ERR_REQUIRE_ESM]: require() of ES Module /home/user/projeect/@app/homepage/node_modules/vitedge/plugin.js from /home/user/project/@app/homepage/vite.config.ts not supported.
Instead change the require of plugin.js in /home/user/projeect/@app/homepage/vite.config.ts to a dynamic import() which is available in all CommonJS modules.
    at Object.<anonymous> (/home/user/projeect/@app/homepage/vite.config.ts:37:32)
    at Object.require.extensions.<computed> [as .ts] (/home/user/projeect/node_modules/vite/dist/node/chunks/dep-98dbe93b.js:76005:20)
    at loadConfigFromBundledFile (/home/user/projeect/node_modules/vite/dist/node/chunks/dep-98dbe93b.js:76013:17)
    at loadConfigFromFile (/home/user/projeect/node_modules/vite/dist/node/chunks/dep-98dbe93b.js:75932:32)
    at async resolveConfig (/home/user/projeect/node_modules/vite/dist/node/chunks/dep-98dbe93b.js:75504:28)
    at async getProjectInfo (file:///home/user/projeect/@app/homepage/node_modules/vitedge/config.js:5:18)
    at async file:///home/user/projeect/@app/homepage/node_modules/vitedge/bin/cli.js:38:22 {
  code: 'ERR_REQUIRE_ESM'
}

import statement is import vitedge from "vitedge/plugin.js";

env:
node v16.7.0 but also tried 14.17.0
vite v2.4.4
typescript 4.4.1-rc
vitedge: 0.15.1 but also tried 0.15.0 and 0.14.0

Any ideeas? Thanks!

React support?

I think this is a really neat way to do things for workers. Would have an example do the same with react-ssr?

Could not get page props for route "home"

Could not get page props for route "home"
FetchError: invalid json response body at http://localhost:3000/props/login?propsGetter=%2Fprops%2Fhome&data=%257B%2522name%2522%253A%2522home%2522%252C%2522path%2522%253A%2522%252F%2522%252C%2522fullPath%2522%253A%2522%252Flogin%2522%252C%2522query%2522%253A%257B%257D%252C%2522params%2522%253A%257B%257D%257D reason: Unexpected token < in JSON at position 0

Not so sure, how to fix this one? Can we do any better error messaging on this one? I can help with the docs.

Error: Invalid module “node_modules/.bin/vite-ssr”

Error: Invalid module “node_modules/.bin/vite-ssr”

#74 ⬅️ I had a similar problem to this in version 0.18.1
I had a problem configuring my own vitedge-time project, and then used the initial https://github.com/frandiox/vitessedge-template project as the minimum unit test, which also reproduced the problem

node: v16.13.0;
npm: 8.1.0;
system: MacOS;

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "file:///Users/rizumu/Downloads/vitessedge-template-master/node_modules/.bin/vite-ssr" 
    at new NodeError (node:internal/errors:371:5)
    at ESMLoader.load (node:internal/modules/esm/loader:324:13)
    at async ESMLoader.moduleProvider (node:internal/modules/esm/loader:230:47)
    at async link (node:internal/modules/esm/module_job:67:21) {
  code: 'ERR_INVALID_MODULE_SPECIFIER'
}

image

Environment variables arent accessible in app level?

I have added a .env in root with

VITEDGE_TEST="hello world"
HELLO_TEST="hello world"

Then trying to access them in src/App.jsx like so

function App ({ router }) {
  console.log(process.env.VITEDGE_TEST, process.env.HELLO_TEST)

but both are undefined. Am I doing something wrong?

@emotion/server support

Emotion is a popular alternative to styled-components in the React world, though Emotion also works with other frameworks as well. Material-UI v5 is moving towards Emotion instead of running their own custom styles. It might be worth adding support for Emotion in place of Material-UI v4 because it would support a wider range of projects.

Loading Root Page via SSR ends up in SyntaxError: Unexpected end of JSON input

https://pokedex-vitedge.hrgui.workers.dev/ leads to error 1101

In wrangler dev:

Uncaught
SyntaxError: Unexpected end of JSON input
Uncaught (in promise)
SyntaxError: Unexpected end of JSON input
Uncaught (in response)
SyntaxError: Unexpected end of JSON input

While https://pokedex-vitedge.hrgui.workers.dev/pokemon/1 works, then navigating home works also. However, refreshing the page there doesn't work.

See https://github.com/hrgui/pokedex-vitedge for repo

  1. yarn && yarn build in root
  2. npm install && wrangler dev in workers-site

Rollup failed to resolve image absolute path on build

Currently when I run build I run into a rollup import error.

[vite]: Rollup failed to resolve import "/logo.svg" from "src/components/Footer.vue".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
/vite-edge/node_modules/vite/dist/node/chunks/dep-e0fe87f8.js:43253
            throw new Error(`[vite]: Rollup failed to resolve import "${id}" from "${importer}".\n` +
                  ^

Error: [vite]: Rollup failed to resolve import "/logo.svg" from "src/components/Footer.vue".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`

This works when running dev, but not when running build, but when I switch to relative path, build runs perfectly.
And vite warning is on when using relative path for dev.

Using Material-UI v4's Hidden components breaks client-side render

Using the Hidden component for responsive rendering seems to work server-side but completely breaks the client-side hydration.
https://material-ui.com/api/hidden/

Uncaught (in promise) Error: HiddenJs(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

The above error occurred in the <HiddenJs> component:
HiddenJs@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:9011:19
WithWidth@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:8961:26
Hidden@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:9146:31
div
Grid2@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:8416:29
WithStyles2@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5767:29
div
Grid2@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:8416:29
WithStyles2@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5767:29
div
Container2@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:5251:17
WithStyles2@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5767:29
DesktopNavbar2@http://localhost:3000/node_modules/.vite/@mythicalgames_blankos-ui.js?v=8348f43e:1886:23
HiddenJs@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:9011:19
WithWidth@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:8961:26
Hidden@http://localhost:3000/node_modules/.vite/chunk-M6O6TB7Y.js?v=8348f43e:9146:31
menu
StyledComponent2@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5666:22
HeaderNavigation2@http://localhost:3000/node_modules/.vite/@mythicalgames_blankos-ui.js?v=8348f43e:1929:17
Header
div
StyledComponent2@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5666:22
Layout@http://localhost:3000/src/components/layout/Layout.tsx?t=1626122462887:34:16
ThemeProvider@http://localhost:3000/node_modules/.vite/chunk-4I5GE5QD.js?v=8348f43e:5124:18
MythicalThemeProvider2@http://localhost:3000/node_modules/.vite/@mythicalgames_blankos-ui.js?v=8348f43e:1477:18
App@http://localhost:3000/src/App.tsx?t=1626122462887:25:13
Suspense
Router2@http://localhost:3000/node_modules/.vite/chunk-DUYGPITX.js?v=8348f43e:1449:30
BrowserRouter2@http://localhost:3000/node_modules/.vite/chunk-DUYGPITX.js?v=8348f43e:1988:35
r2@http://localhost:3000/node_modules/.vite/chunk-R2XQJXG7.js?v=8348f43e:354:21

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.

Routes with no props handler returning 404 status

routes that do not have props-handler returning 404 status for the rendered document even though the content is correct.

I think this can be solved by either making sure the renderer always return a status code or
avoiding NotFoundResponse for missing props here

response: createNotFoundResponse(),

I can make a PR if the issue is acknowledged

Thanks

Vite error in dev ssr mode: Failed to resolve entry for package "vitedge".

To create a basic app with vitedge, I started with the guide on the docs. I did pretty much everything from the start a couple of times but there were always the same results.

But when I tried to run yarn dev, the following error occured:

Failed to resolve entry for package "vitedge". The package may have incorrect main/module/exports specified in its package.json: Failed to resolve entry for package "vitedge". The package may have incorrect main/module/exports specified in its package.json.

    at packageEntryFailure (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:30629:11)
    at resolvePackageEntry (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:30625:9)
    at tryNodeResolve (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:30448:20)
    at viteResolve (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:67795:26)
    at nodeImport (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:67823:15)
    at ssrImport (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:67731:20)
    at eval (/home/waff/workspace/wvffle.net/src/main.js:3:37)
    at instantiateModule (/home/waff/workspace/wvffle.net/node_modules/vite/dist/node/chunks/dep-bc5f477e.js:67776:15

When I try to reload the page myself, the error changes:

resolve is not a function

    at Object.getRenderContext (file:///home/waff/workspace/wvffle.net/node_modules/vitedge/dev/middleware.js:302:22)
    at handleSsrRequest (/home/waff/workspace/wvffle.net/node_modules/vite-ssr/dev/server.js:97:32

Here are my dependencies:

{
  "@vue/server-renderer": "3",
  "@vueuse/head": "^0.7.4",
  "vitedge": "^0.17.3",
  "vue": "3",
  "vue-router": "4"
}
{
  "@vitejs/plugin-vue": "^2.0.0",
  "vite": "^2.7.5"
}

I'm using node 17:

λ ~/workspace/wvffle.net/ node -v
v17.3.0

How to handle CORS?

I have a filename getAllPosts in functions file.
Which return something like this

export default ({
  handler: async function ({ request }) {
   const data = await getData();
    return {
      data: data,
    }
  }
})

Now this works great in the same origin. But when I try to use the same API from anohter origin, it is yelling CORS errors.
What would be the best way to handle CORS in vitedge?

Error during wrangler publish: process is not defined

Hi, @frandiox, I've finally got some more time to try this out. It looks like you've done quite a bit since I last tried vitedge. I've just started a new app and have run into an error during wrangler publish. Here are my steps:

  1. Create the vitessedge app. It looks like it's pre-configured to work with esbuild, which is awesome to see.
  2. Update the wrangler.toml with my app's name and account id.
  3. Run npm run build
  4. Run wrangler publish in the project root and see the following error:

Screen Shot 2021-08-26 at 6 07 20 PM

It seems like maybe the error has something to do with reading environment variables from process.env inside dist/worker/script.js.

I've tried this with the default npm dependency versions in the app and with all deps updated to the latest using npm-check-updates. I get the same results. Do you have any pointers for me? Have you seen this before? Thank you.

I'm running wrangler 1.19.0

Unable to load some site assets.

The issue we are experiencing is one related to KV assets. For context: our site is a Vue 3 application. We are building it using Vitedge to support SSR running on a Cloudflare Worker.

The issue as reported by one of our devs:

  1. We deployed our application using Vitedge. In doing so, we deployed a new worker and KV:  - Worker: test-assets
      - KV: __test-assets-workers_sites_assets (c46d5f240d0643fe9f822ae3a25595de)

  2. We opened the page that the Wrangler GitHub action had spun up for our Worker: https://test-assets.superfarm.workers.dev/ in incognito mode and waited until test-assets redeployed, then navigated to one of our pages ("NFT Drops") and experienced some HTTP 500 errors that some of our assets could not be found.

Error: 1101
Ray ID: 6ccb5309f8d94991
Timestamp: 2022-01-13 02:51:43 UTC
  1. Checking our logs for the 500 error isn't helpful.```  "exceptions": [
        {
          "name": "Error",
          "message": "internal error",
          "timestamp": 1642042501244
        }
      ],

4. Further digging shows that the assets in the URL don't seem to match those that were stored in KV:```
$ wrangler kv:key get assets/Stats.af469f3b.js --namespace-id c46d5f240d0643fe9f822ae3a25595de
Code 10009: get: 'key not found'

The actual asset name:```
$ wrangler kv:key get assets/Stats.af469f3b.9ec2057bc5.js --namespace-id c46d5f240d0643fe9f822ae3a25595de


We're currently assuming that either Vitedge or Wrangler are adding some unique hash to the end of our assets. Vitedge is getting its assets using the `@cloudflare/kv-asset-handler` package so this might be a known issue in that handler?

We would love it if you could share some insights on this or help further debug.

webpack is not working

wondering why Webpack isn't working.

wrangler.toml

name = "vitessedge"
type = "javascript"
account_id = "***"
workers_dev = true
route = ""
zone_id = ""
webpack_config = "webpack.config.js"

[site]
bucket = "dist/client"
entry-point = "."

[build]
command = ""
watch_dir = "dist/worker"

[build.upload]
format = "service-worker"

webpack.config.js

module.exports = {
  ...require('vitedge/webpack.cjs')(options),
}

package.json:

main in package.json is set to dist/worker/script.js and type: module is removed

Error:

resolve './index' in '/home/dmarx/projects/project-docs'
  using description file: /home/dmarx/projects/project-docs/package.json (relative path: .)
    Field 'browser' doesn't contain a valid alias configuration
    using description file: /home/dmarx/projects/project-docs/package.json (relative path: ./index)
      no extension
        Field 'browser' doesn't contain a valid alias configuration
        /home/dmarx/projects/project-docs/index doesn't exist
      .wasm
        Field 'browser' doesn't contain a valid alias configuration
        /home/dmarx/projects/project-docs/index.wasm doesn't exist
      .mjs
        Field 'browser' doesn't contain a valid alias configuration
        /home/dmarx/projects/project-docs/index.mjs doesn't exist
      .js
        Field 'browser' doesn't contain a valid alias configuration
        /home/dmarx/projects/project-docs/index.js doesn't exist
      .json
        Field 'browser' doesn't contain a valid alias configuration
        /home/dmarx/projects/project-docs/index.json doesn't exist
      as directory
        /home/dmarx/projects/project-docs/index doesn't exist
Error: webpack returned an error. Try configuring `entry` in your webpack config relative to the current working directory, or setting `context = __dirname` in your webpack config.

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.