-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scaffolding Logic #2
Comments
This sounds interessting, but unfortunately I have worked with |
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 In prod, our transformer removes all macros and picks only one if-block. |
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 So yes, multiple variants in one file. We can still have variant files such as 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
That way the npm package 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. |
Yeah, this is totally interessting and fun!⚡Yesterday i was researching for more then 10 hours the topics
This will be great! I optimized my code a lot, but I still want to spent some more time, switching to |
👌 |
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 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 |
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 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. |
I edited this post, since I remember you said we can have single files for the components e.g.
I think we even must do this, since its not possible to dynamic import // 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. |
💯 |
Today was very successful. I'll clean up the code and update the repo for sure if I'm ready. 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. 😎 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
} |
Looks good. Can't wait to see it all come together 👀. |
I updated the repo. |
Nice, will have a look at it later today. Ok 👌. |
New commit is done! Still some stuff todo ⚡ |
⚡⚡⚡Neat neat neat. :-). All-in-all it's great. Only thing:
As said in PM, I love many details about your code. It's interesting that you put the reducers along TypeScript types in |
Awesome that you like it 😄
I was thinking the same while developing, but realized when I replaced the This is really making fun, I will continue afap. |
How about this:
The neat thing here is that we simply develop one big app that contains all variants. We can use IntelliSense, TypeScript, etc. just like a normal app.
Maybe we can remove the irrelevant if-blocks without any AST:
if (import.meta.IS_PREACT) {
(we throw an error if we detect non-prettier syntax, e.g.if(import.meta.IS_PREACT){
.}
with the same space padding. (Whenprettier
is applied I believe this is a reliable way to get the end of the block.)Or we use an AST, whatever seems easiest.
@cyco130 FYI (author of https://github.com/cyco130/create-vike)
The text was updated successfully, but these errors were encountered: