Giter VIP home page Giter VIP logo

Comments (16)

brillout avatar brillout commented on July 25, 2024 1

Looks good. Can't wait to see it all come together πŸ‘€.

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

This sounds interessting, but unfortunately I have worked with import.meta just in one scenario until now.
Can you please provide a more detailed example of the concept?

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

import.meta doesn't matter here. It's only about replacing/removing static strings. It could be MACRO_PREACT instead of import.meta.IS_PREACT.

We could, for example, use Vite to do this. We can implement a Vite plugin that does these transformations.

In dev, our transfomer replaces these macros with true/fasle.

In prod, our transformer removes all macros and picks only one if-block.

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

For example:

// renderer/_default.page.server.tsx

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
let renderToString: (element: unknown) => string
let PageShell: unknown
if (UI === 'react') {
  renderToString = (await import('react-dom/server')).renderToString
  PageShell = (await import('./PageShell.react.tsx')).PageShell
}
if (UI === 'preact') {
  renderToString = (await import('preact-render-to-string'))
  PageShell = (await import('./PageShell.preact.tsx')).PageShell
}

// We can reuse a single boilerplate code for both the react and preact variants πŸ‘Œ.
export function render(pageContext) {
  const { Page, pageProps } = pageContext

  const pageHtml = renderToString(
    <PageShell>
      <Page {...pageProps} />
    </PageShell>,
  )

  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="page-view">${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`
}

(I think if (UI === 'react') is better than if (MACRO_PREACT) for better TypeScript ergonomics.)

So yes, multiple variants in one file. We can still have variant files such as PageShell.react.tsx and PageShell.preact.tsx.

The Vite transformer would prune if-blocks, so that only one if-block remains.

I'd suggest we try to prune if-blocks without AST first.

Without AST because we can then publish an npm package create-vite-plugin-ssr that contains only:

  1. The "big boilerplate" that contains all variants
  2. The variant pruning code

That way the npm package create-vite-plugin-ssr stays light.

Note that this scaffoling technology we are developing is more than just about vite-plugin-ssr.

It will enable things like https://divjoy.com/ but 10x better. Last time I checked it seemed like the author of Divjoy abandoned the project. I'm guessing because Divjoy become unmaintainable. (Scaffolding logic is not easy, as we can see in this very thread :-).)

This is exciting.

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

Scaffolding logic is not easy, as we can see in this very thread :-)
This is exciting.

Yeah, this is totally interessting and fun!⚑Yesterday i was researching for more then 10 hours the topics micro-frontends & single-SPA. I have read many examples and strategies about and now I can follow your thoughts.

That way the npm package create-vite-plugin-ssr stays light.

This will be great!

I optimized my code a lot, but I still want to spent some more time, switching to pnpm and setting up a new project structure. I will update the repo asap. πŸ’―

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

πŸ‘Œ

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

Oki, so I'm a bit stuck at the moment, cause I don't understand totally the concept you have in mind.

When I look at micro-frontend solutions like micro-app, or micro-zoe, it seems like a different approach.

When I look at create-vike, it also seems different, since it uses static content to generate a boilerplate/template.

When we use Vite with conditionally dynamic imports, I don't understand how we want to build/generate a boilerplate, which then installs, or can be used by a user. As far as I understand, if you run vite build with the conditionally dynamic imports, it will compile the source to javascript, what can't be actually used for development, since its no source code.

I have setup a kind of monorepo with the CLI-app, and a second app with vite, the "big boilerplate", but I don't know how to continue. What I was thinking, that the CLI could generate a .env file, with the selectedOptions, which then can be used as import.meta inside the boilerplate and as .env vars in vite.

2022-04-21_01h54_03

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

Have a look at https://vitejs.dev/guide/api-plugin.html. Play around with it, e.g. implement a toy Vite plugin and apply it on a vanilla Vite app (without vite-plugin-ssr). Alternatively, we can also simply use import.meta.env as you suggested πŸ‘.

We use Vite only for dev (when we work on developing the big boilerplate).

We don't use Vite to build. Instead we simply use JavaScript. For example:

// generateBoilerplate.ts

type Options = { framework: 'react' | 'preact', clientRouting: boolean }

export function generateBoilerplate(options: Options) {
  let boilerplateFiles = findBoilerplateFiles()

  boilerplateFiles = boilerplateFiles.map(file => {
    file.code = removeIfBlocks(file.code, options)
    return file
  })

  // ...
}

function findBoilerplateFiles(): { filePath: string, code: string }[] {
 // Crawl and get all the boilerplate `.ts` files
}

function removeIfBlocks(code: string, options: Options) {
   Object.entries(options).forEach(([optionName, optionValue]) => {
     const lines = code.split('\n')

     let state: 'OUTSIDE_IF_BLOCK' | 'INSIDE_IF_BLOCK' = 'OUTSIDE_IF_BLOCK'
     let whitespacePadding: null | number = null

     lines = lines.filter((line, i) => {
       const idx = lines.findIndex(`if (import.meta.env.${optionName}`)
       if (idx !== -1) {
         state = 'INSIDE_IF_BLOCK'
         whitespacePadding = idx
         return false // We always remove the if condition lines
       }

       if (
         state === 'INSIDE_IF_BLOCK' && line.trim() === '}' &&
         line.length === whitespacePadding.length + 1
       ) {
         state = 'OUTSIDE_IF_BLOCK'
         whitespacePadding = null
         return false
       }

       // We keep the lines that are outside the if-block.
       if (state === 'OUTSIDE_IF_BLOCK') {
         return true
       }

       if (state === 'INSIDE_IF_BLOCK') {
         // TODO: only remove if value is `!== optionValue`: if the value is `=== optionValue`
         // we should keep the block content.
         return false
       }
     })

     code = lines.join('\n')
   })

   // We should have no `import.meta.env` left after we removed the if-blocks. (We remove the
   // if-condition for the one if-block we keep.)
   assert(!code.includes('import.meta.env'), "Wrong `import.meta.env` syntax. Make sure to apply prettier.")

   return code
}

AFAICT we don't need an AST.

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

I edited this post, since I remember you said we can have single files for the components e.g.

index.page.preact.tsx
index.page.react.tsx
index.page.vue

I think we even must do this, since its not possible to dynamic import hooks inside a condition.

// TypeError: useState is not a function or its return value is not iterable
let useState: any
// OR
let useState: (element: any) => any

if (import.meta.env.VITE_APP_FRAMEWORK === 'React') {
  useState = (await import('react')).useState
}

So all good, I will continue tomorrow.

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

πŸ’―

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

Today was very successful. I'll clean up the code and update the repo for sure if I'm ready.
As long, I couldn't get your pruning code example to work with array.IndexOd and wildcards, but with line.includes.

lines.findIndex(`if (import.meta.env.${optionName}`)

Nevermind, to keep this up to date here is the working code snippet I got so far, with a test string. 😎
If you have any ideas how to optimize let me know.

import assert from 'assert'

const frameworks = ['Preact', 'React', 'Vue'] as const

type Frameworks = typeof frameworks[number]

type Framework = Partial<Frameworks>

type Options = { VITE_APP_FRAMEWORK: Framework }

const VITE_APP_FRAMEWORK = 'React'

const options: Options = {
  VITE_APP_FRAMEWORK
}

const rExp = new RegExp(VITE_APP_FRAMEWORK, 'm') as unknown as Framework

generateBoilerplate(options)

export function generateBoilerplate(options: Options) {
  let boilerplateFiles = findBoilerplateFiles()

  boilerplateFiles = boilerplateFiles.map((file) => {
    file.code = removeIfBlocks(file.code, options)
    return file
  })

  // ...
}

function findBoilerplateFiles(): { filePath: string; code: string }[] {
  return [
    {
      filePath: `./boilerplate.ts`,
      code: `if (import.meta.env.VITE_APP_FRAMEWORK === 'React') {
  console.log('SELECTED REACT')
}
if (import.meta.env.VITE_APP_FRAMEWORK === 'Vue') {
  console.log('SELECTED VUE')
}
if (import.meta.env.VITE_APP_FRAMEWORK === 'Preact') {
  console.log('SELECTED PREACT')
}
console.log('END1')
console.log('END2')
console.log('END3')`
    }
  ]
}

function removeIfBlocks(code: string, options: Options) {
  Object.entries(options).forEach(([optionName, optionValue]) => {
    let lines = code.split('\n')

    let state: 'OUTSIDE_IF_BLOCK' | 'INSIDE_IF_BLOCK' = 'OUTSIDE_IF_BLOCK'
    let whitespacePadding: null | number = null
    let frameworkValue: any = null

    lines = lines.filter((line, i) => {
      const idx = line.includes(`if (import.meta.env.${optionName}`)

      if (idx) {
        frameworkValue = line.match(rExp)
        state = 'INSIDE_IF_BLOCK'
        whitespacePadding = i
        return false // We always remove the if condition lines
      }

      if (state === 'INSIDE_IF_BLOCK' && line.trim() === '}' && i === whitespacePadding + 2) {
        state = 'OUTSIDE_IF_BLOCK'
        whitespacePadding = null
        return false
      }

      // We keep the lines that are outside the if-block.
      if (state === 'OUTSIDE_IF_BLOCK') {
        return true
      }

      if (state === 'INSIDE_IF_BLOCK') {
        // TODO: only remove if value is `!== optionValue`: if the value is `=== optionValue`
        // we should keep the block content.
        return frameworkValue !== null && frameworkValue[0] === optionValue
      }
    })

    code = lines.join('\n')
  })

  console.log(code)

  // We should have no `import.meta.env` left after we removed the if-blocks. (We remove the
  // if-condition for the one if-block we keep.)
  assert(!code.includes('import.meta.env'), 'Wrong `import.meta.env` syntax. Make sure to apply prettier.')

  return code
}

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

I updated the repo.
Really like the CLI code style now! 😍 With the reducer it's sweet.
Next I integrate the generator & optimize the boilerplate package, cause that's a bit dirty. πŸ˜‡

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

Nice, will have a look at it later today.

Ok πŸ‘Œ.

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

New commit is done! Still some stuff todo ⚑

from create-vite-plugin-ssr.

brillout avatar brillout commented on July 25, 2024

⚑⚑⚑Neat neat neat.

:-).

All-in-all it's great. Only thing:

  • I don't know if we need this <TaskList/> thing. Although I really like when the CLI is showing concurrent loading icons. Looks cute, neat, clean, and powerful. Just wondering whether we are overengineering here. But if we do have slow tasks (e.g. running pnpm install on behalf of the user), then yea it does make sense.

As said in PM, I love many details about your code.

It's interesting that you put the reducers along TypeScript types in types.ts. Didn't like it at first, but actually it kind of makes sense. Looking forward to see how it's going to evolve.

from create-vite-plugin-ssr.

jrson83 avatar jrson83 commented on July 25, 2024

Awesome that you like it πŸ˜„

I don't know if we need this thing. Although I really like when the CLI is showing concurrent loading icons. Looks cute, neat, clean, and powerful. Just wondering whether we are overengineering here. But if we do have slow tasks (e.g. running pnpm install on behalf of the user), then yea it does make sense.

I was thinking the same while developing, but realized when I replaced the delay placeholder with real promises. We will see if I add more tasks like detype, which might take longer, or like you say, if we might add a install dependencies option, it will make sense. One thing this is really cool for, when a promise is rejected, so an error happens, you can see it in the progress with icon (I will also add an error message when this happens, in the completed message as next).

This is really making fun, I will continue afap.

from create-vite-plugin-ssr.

Related Issues (7)

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.