create-vitral 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/index.js +7 -0
- package/package.json +47 -3
- package/src/cli.js +53 -0
- package/src/create.js +108 -0
- package/templates/_comum/_gitignore +8 -0
- package/templates/_comum/playwright.config.js +8 -0
- package/templates/host/_base/e2e/host.spec.js +8 -0
- package/templates/host/_base/index.html +11 -0
- package/templates/host/angular/src/main.ts +19 -0
- package/templates/host/angular/tsconfig.json +13 -0
- package/templates/host/lit/src/main.js +13 -0
- package/templates/host/next/app/layout.jsx +9 -0
- package/templates/host/next/app/page.jsx +11 -0
- package/templates/host/nuxt/app/app.vue +13 -0
- package/templates/host/nuxt/nuxt.config.ts +4 -0
- package/templates/host/preact/src/main.jsx +10 -0
- package/templates/host/react/src/main.jsx +9 -0
- package/templates/host/solid/src/main.jsx +12 -0
- package/templates/host/sveltekit/src/app.html +11 -0
- package/templates/host/sveltekit/src/routes/+page.svelte +9 -0
- package/templates/host/vanilla/src/main.js +8 -0
- package/templates/host/vue/src/App.vue +12 -0
- package/templates/host/vue/src/main.js +4 -0
- package/templates/remote/_base/e2e/remote.spec.js +8 -0
- package/templates/remote/_base/index.html +14 -0
- package/templates/remote/angular/src/App.ts +16 -0
- package/templates/remote/angular/tsconfig.json +13 -0
- package/templates/remote/lit/src/App.js +19 -0
- package/templates/remote/preact/src/App.jsx +12 -0
- package/templates/remote/react/src/App.jsx +12 -0
- package/templates/remote/solid/src/App.jsx +12 -0
- package/templates/remote/svelte/src/App.svelte +10 -0
- package/templates/remote/vanilla/src/App.js +12 -0
- package/templates/remote/vue/src/App.vue +14 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 João (joaogabriel15)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-vitral",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Cria um host ou um remote vitral: npm create vitral@latest",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vitral",
|
|
7
|
+
"micro-frontend",
|
|
8
|
+
"micro-frontends",
|
|
9
|
+
"microfrontends",
|
|
10
|
+
"create",
|
|
11
|
+
"scaffold",
|
|
12
|
+
"template",
|
|
13
|
+
"cli",
|
|
14
|
+
"vite",
|
|
15
|
+
"react",
|
|
16
|
+
"vue",
|
|
17
|
+
"angular",
|
|
18
|
+
"svelte"
|
|
19
|
+
],
|
|
20
|
+
"homepage": "https://github.com/joaogabriel15/vitral#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/joaogabriel15/vitral/issues"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/joaogabriel15/vitral.git",
|
|
27
|
+
"directory": "packages/create-vitral"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "João (https://github.com/joaogabriel15)",
|
|
31
|
+
"type": "module",
|
|
32
|
+
"bin": {
|
|
33
|
+
"create-vitral": "./index.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"index.js",
|
|
37
|
+
"src",
|
|
38
|
+
"templates"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.19"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public",
|
|
45
|
+
"provenance": true
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "node --test test/create.test.js"
|
|
49
|
+
}
|
|
6
50
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises'
|
|
2
|
+
import { create, DEFAULT_SRC, HOSTS, REMOTES } from './create.js'
|
|
3
|
+
|
|
4
|
+
export const AJUDA = `Uso: npm create vitral@latest [pasta] -- [--host | --remote] [--framework nome] [--src url]
|
|
5
|
+
|
|
6
|
+
--host, --remote o que criar
|
|
7
|
+
--framework, -f hosts: ${HOSTS.join(', ')}
|
|
8
|
+
remotes: ${REMOTES.join(', ')} (next, nuxt e sveltekit viram react, vue e svelte)
|
|
9
|
+
--src vitral.json que o host carrega (padrão: ${DEFAULT_SRC})
|
|
10
|
+
|
|
11
|
+
Sem as opções, o comando pergunta.`
|
|
12
|
+
|
|
13
|
+
/** @param {string[]} argv */
|
|
14
|
+
export function parseArgs(argv) {
|
|
15
|
+
const o = {}
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
const a = argv[i]
|
|
18
|
+
if (a === '--host' || a === '--remote') o.kind = a.slice(2)
|
|
19
|
+
else if (a === '--framework' || a === '-f') o.framework = argv[++i]
|
|
20
|
+
else if (a === '--src') o.src = argv[++i]
|
|
21
|
+
else if (a === '--help' || a === '-h') o.help = true
|
|
22
|
+
else if (a.startsWith('-')) throw new Error(`Opção desconhecida: ${a}`)
|
|
23
|
+
else o.dir = a
|
|
24
|
+
}
|
|
25
|
+
return o
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string[]} argv
|
|
30
|
+
* @param {{ ask?: (q: string) => Promise<string>, log?: (m: string) => void }} [io]
|
|
31
|
+
*/
|
|
32
|
+
export async function run(argv, { ask, log = console.log } = {}) {
|
|
33
|
+
const o = parseArgs(argv)
|
|
34
|
+
if (o.help) return log(AJUDA)
|
|
35
|
+
let rl
|
|
36
|
+
const question = ask ?? ((q) => (rl ??= createInterface({ input: process.stdin, output: process.stdout })).question(q))
|
|
37
|
+
try {
|
|
38
|
+
o.dir ??= (await question('Pasta (meu-app): ')).trim() || 'meu-app'
|
|
39
|
+
o.kind ??= /^r/i.test(await question('Host ou remote? (h/r): ')) ? 'remote' : 'host'
|
|
40
|
+
const list = o.kind === 'host' ? HOSTS : REMOTES
|
|
41
|
+
if (!o.framework) {
|
|
42
|
+
const menu = list.map((f, i) => `${i + 1}. ${f}`).join(' ')
|
|
43
|
+
const answer = (await question(`Framework: ${menu}\n> `)).trim()
|
|
44
|
+
o.framework = list[Number(answer) - 1] ?? (answer || list[0])
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
rl?.close()
|
|
48
|
+
}
|
|
49
|
+
const files = create(o)
|
|
50
|
+
log(`\nPronto: ${o.kind} ${o.framework} em ${o.dir}\n\n cd ${o.dir}\n npm install\n npm run dev\n`)
|
|
51
|
+
if (o.kind === 'host') log(`O host carrega ${o.src ?? DEFAULT_SRC}. Suba o remote antes.`)
|
|
52
|
+
return files
|
|
53
|
+
}
|
package/src/create.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Gera um host ou um remote a partir de templates/. Sem dependências.
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url))
|
|
7
|
+
// vitral e create-vitral saem com a mesma versão ("fixed" no .changeset/config.json).
|
|
8
|
+
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
9
|
+
|
|
10
|
+
export const HOSTS = ['react', 'next', 'vue', 'nuxt', 'angular', 'sveltekit', 'solid', 'preact', 'lit', 'vanilla']
|
|
11
|
+
export const REMOTES = ['react', 'vue', 'svelte', 'solid', 'preact', 'angular', 'lit', 'vanilla']
|
|
12
|
+
/** Frameworks de app inteiro viram o remote do framework de componentes. */
|
|
13
|
+
export const REMOTE_OF = { next: 'react', nuxt: 'vue', sveltekit: 'svelte' }
|
|
14
|
+
export const DEFAULT_SRC = 'http://localhost:5174/vitral.json'
|
|
15
|
+
|
|
16
|
+
const ANGULAR = ['@angular/core', '@angular/common', '@angular/compiler', '@angular/platform-browser'].map((d) => [d, '^22.0.0'])
|
|
17
|
+
const ANGULAR_DEV = { '@analogjs/vite-plugin-angular': '^2.8.0', '@angular/build': '^22.0.0', '@angular/compiler-cli': '^22.0.0', typescript: '~6.0.0' }
|
|
18
|
+
// fileURLToPath e não `.pathname`: no Windows e em pastas com espaço ou acento
|
|
19
|
+
// `.pathname` dá "/C:/..." ou "%20" e o plugin não acha o tsconfig.
|
|
20
|
+
const ANGULAR_TS = "angular({ tsconfig: fileURLToPath(new URL('./tsconfig.json', import.meta.url)) })"
|
|
21
|
+
const ANGULAR_IMPORT = "import { fileURLToPath } from 'node:url'\nimport angular from '@analogjs/vite-plugin-angular'"
|
|
22
|
+
|
|
23
|
+
// framework → [dependências, devDependencies, import do plugin do Vite, chamada]
|
|
24
|
+
const VITE = {
|
|
25
|
+
react: [{ react: '^19.0.0', 'react-dom': '^19.0.0' }, { '@vitejs/plugin-react': '^6.0.0' }, "import react from '@vitejs/plugin-react'", 'react()'],
|
|
26
|
+
vue: [{ vue: '^3.5.0' }, { '@vitejs/plugin-vue': '^6.0.0' }, "import vue from '@vitejs/plugin-vue'", 'vue()'],
|
|
27
|
+
svelte: [{ svelte: '^5.0.0' }, { '@sveltejs/vite-plugin-svelte': '^7.0.0' }, "import { svelte } from '@sveltejs/vite-plugin-svelte'", 'svelte()'],
|
|
28
|
+
sveltekit: [{ svelte: '^5.0.0' }, { '@sveltejs/kit': '^3.0.0', '@sveltejs/vite-plugin-svelte': '^7.0.0' }, "import { sveltekit } from '@sveltejs/kit/vite'", 'sveltekit()'],
|
|
29
|
+
solid: [{ 'solid-js': '^1.9.0' }, { 'vite-plugin-solid': '^2.11.0' }, "import solid from 'vite-plugin-solid'", 'solid()'],
|
|
30
|
+
preact: [{ preact: '^10.0.0' }, { '@preact/preset-vite': '^2.10.0' }, "import preact from '@preact/preset-vite'", 'preact()'],
|
|
31
|
+
angular: [{ ...Object.fromEntries(ANGULAR), rxjs: '^7.8.0', tslib: '^2.8.0' }, ANGULAR_DEV, ANGULAR_IMPORT, ANGULAR_TS],
|
|
32
|
+
lit: [{ lit: '^3.3.0' }, {}, '', ''],
|
|
33
|
+
vanilla: [{}, {}, '', ''],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Hosts fora do Vite: [dependências, devDependencies, script dev, porta]
|
|
37
|
+
const APPS = {
|
|
38
|
+
next: [{ next: '^16.0.0', react: '^19.0.0', 'react-dom': '^19.0.0' }, {}, 'next dev', 3000],
|
|
39
|
+
nuxt: [{ nuxt: '^4.0.0', vue: '^3.5.0' }, {}, 'nuxt dev', 3000],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {{ dir: string, kind: 'host' | 'remote', framework: string, src?: string }} o
|
|
44
|
+
* @returns {string[]} arquivos criados, relativos a `dir`
|
|
45
|
+
*/
|
|
46
|
+
export function create({ dir, kind, framework, src = DEFAULT_SRC }) {
|
|
47
|
+
if (kind === 'remote') framework = REMOTE_OF[framework] ?? framework
|
|
48
|
+
const list = kind === 'host' ? HOSTS : REMOTES
|
|
49
|
+
if (!list.includes(framework)) throw new Error(`Template desconhecido: ${kind} ${framework}. Opções: ${list.join(', ')}.`)
|
|
50
|
+
if (existsSync(dir) && readdirSync(dir).length) throw new Error(`A pasta ${dir} não está vazia.`)
|
|
51
|
+
const name = basename(resolve(dir)).toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
52
|
+
const app = APPS[framework]
|
|
53
|
+
const port = kind === 'remote' ? 5174 : (app?.[3] ?? 5173)
|
|
54
|
+
const vars = { __NAME__: name, __SRC__: src, __PORT__: String(port) }
|
|
55
|
+
|
|
56
|
+
mkdirSync(dir, { recursive: true })
|
|
57
|
+
copy(join(TEMPLATES, kind, framework), dir)
|
|
58
|
+
// index.html e e2e comuns. Next, Nuxt e SvelteKit têm a própria página.
|
|
59
|
+
const ownHtml = app || framework === 'sveltekit'
|
|
60
|
+
copy(join(TEMPLATES, kind, '_base'), dir, (f) => !ownHtml || !f.endsWith('.html'))
|
|
61
|
+
copy(join(TEMPLATES, '_comum'), dir)
|
|
62
|
+
renameSync(join(dir, '_gitignore'), join(dir, '.gitignore'))
|
|
63
|
+
|
|
64
|
+
const [deps, dev, imp, call] = app ? [app[0], app[1]] : VITE[framework]
|
|
65
|
+
const pkg = {
|
|
66
|
+
name,
|
|
67
|
+
version: '0.0.0',
|
|
68
|
+
private: true,
|
|
69
|
+
type: 'module',
|
|
70
|
+
scripts: { dev: app ? app[2] : 'vite', build: app ? app[2].replace('dev', 'build') : 'vite build', 'test:e2e': 'playwright test' },
|
|
71
|
+
dependencies: { vitral: `^${version}`, ...deps },
|
|
72
|
+
devDependencies: { ...dev, ...(app ? {} : { vite: '^8.0.0' }), '@playwright/test': '^1.56.0' },
|
|
73
|
+
}
|
|
74
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n')
|
|
75
|
+
if (!app) writeFileSync(join(dir, 'vite.config.js'), viteConfig(kind, framework, imp, call))
|
|
76
|
+
|
|
77
|
+
const files = walk(dir).map((f) => relative(dir, f).split(sep).join('/'))
|
|
78
|
+
const main = files.find((f) => /^src\/main\./.test(f))
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
const path = join(dir, f)
|
|
81
|
+
const text = readFileSync(path, 'utf8')
|
|
82
|
+
const out = text.replace(/__[A-Z]+__/g, (k) => (k === '__MAIN__' ? `/${main}` : (vars[k] ?? k)))
|
|
83
|
+
if (out !== text) writeFileSync(path, out)
|
|
84
|
+
}
|
|
85
|
+
return files.sort()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function viteConfig(kind, framework, imp, call) {
|
|
89
|
+
const remote = kind === 'remote'
|
|
90
|
+
const imports = ["import { defineConfig } from 'vite'", imp, remote && "import vitral from 'vitral/vite'"].filter(Boolean)
|
|
91
|
+
const plugins = ` plugins: [${[call, remote && 'vitral()'].filter(Boolean).join(', ')}],`
|
|
92
|
+
const body = remote
|
|
93
|
+
? ` // O remote é servido de qualquer pasta da CDN; em dev, na porta 5174.\n base: './',\n${plugins}\n server: { port: 5174 },`
|
|
94
|
+
: framework === 'vanilla' || framework === 'lit'
|
|
95
|
+
? // `vitral/element` é importado dinamicamente: sem isto o Vite só o descobre
|
|
96
|
+
// depois de abrir a página e a recarrega no meio do carregamento.
|
|
97
|
+
`${plugins}\n optimizeDeps: { include: ['vitral/auto', 'vitral/element'] },`
|
|
98
|
+
: plugins
|
|
99
|
+
return `${imports.join('\n')}\n\nexport default defineConfig({\n${body}\n})\n`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function copy(from, to, filter = () => true) {
|
|
103
|
+
if (existsSync(from)) cpSync(from, to, { recursive: true, filter: (f) => filter(f) })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function walk(dir) {
|
|
107
|
+
return readdirSync(dir).flatMap((f) => (statSync(join(dir, f)).isDirectory() ? walk(join(dir, f)) : [join(dir, f)]))
|
|
108
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
// Rode uma vez: npx playwright install chromium
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
testDir: 'e2e',
|
|
6
|
+
use: { baseURL: 'http://localhost:__PORT__' },
|
|
7
|
+
webServer: { command: 'npm run dev', url: 'http://localhost:__PORT__', reuseExistingServer: true },
|
|
8
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
// Suba o remote antes: npm run dev na pasta dele (__SRC__).
|
|
4
|
+
test('o host mostra o remote', async ({ page }) => {
|
|
5
|
+
await page.goto('/')
|
|
6
|
+
await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
|
|
7
|
+
await expect(page.getByRole('heading', { name: 'Olá, host' })).toBeVisible()
|
|
8
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'
|
|
2
|
+
import { bootstrapApplication } from '@angular/platform-browser'
|
|
3
|
+
import 'vitral/auto'
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'app-root',
|
|
7
|
+
// <vitral-remote> é um Web Component: o Angular só precisa deixar passar.
|
|
8
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
9
|
+
template: `
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Host Angular</h1>
|
|
12
|
+
<vitral-remote src="__SRC__" [props]="{ nome: 'host' }"></vitral-remote>
|
|
13
|
+
</main>
|
|
14
|
+
`,
|
|
15
|
+
})
|
|
16
|
+
class App {}
|
|
17
|
+
|
|
18
|
+
document.getElementById('app')!.append(document.createElement('app-root'))
|
|
19
|
+
bootstrapApplication(App)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"useDefineForClassFields": false,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"lib": ["ES2022", "DOM"]
|
|
10
|
+
},
|
|
11
|
+
"files": ["src/main.ts"],
|
|
12
|
+
"angularCompilerOptions": { "strictTemplates": true }
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LitElement, html } from 'lit'
|
|
2
|
+
import 'vitral/auto'
|
|
3
|
+
|
|
4
|
+
class HostApp extends LitElement {
|
|
5
|
+
render() {
|
|
6
|
+
return html`<main>
|
|
7
|
+
<h1>Host Lit</h1>
|
|
8
|
+
<vitral-remote src="__SRC__" .props=${{ nome: 'host' }}></vitral-remote>
|
|
9
|
+
</main>`
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
customElements.define('host-app', HostApp)
|
|
13
|
+
document.getElementById('app').append(new HostApp())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// <Remote> já é 'use client': renderiza o fallback no servidor e monta no cliente.
|
|
2
|
+
import { Remote } from 'vitral/react'
|
|
3
|
+
|
|
4
|
+
export default function Page() {
|
|
5
|
+
return (
|
|
6
|
+
<main>
|
|
7
|
+
<h1>Host Next</h1>
|
|
8
|
+
<Remote src="__SRC__" props={{ nome: 'host' }} fallback={<p>Carregando…</p>} />
|
|
9
|
+
</main>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- <VitralRemote> renderiza o fallback no servidor e monta no cliente. -->
|
|
2
|
+
<script setup>
|
|
3
|
+
import { VitralRemote } from 'vitral/vue'
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<main>
|
|
8
|
+
<h1>Host Nuxt</h1>
|
|
9
|
+
<VitralRemote src="__SRC__" :props="{ nome: 'host' }">
|
|
10
|
+
<template #fallback><p>Carregando…</p></template>
|
|
11
|
+
</VitralRemote>
|
|
12
|
+
</main>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRoot } from 'react-dom/client'
|
|
2
|
+
import { Remote } from 'vitral/react'
|
|
3
|
+
|
|
4
|
+
createRoot(document.getElementById('app')).render(
|
|
5
|
+
<main>
|
|
6
|
+
<h1>Host React</h1>
|
|
7
|
+
<Remote src="__SRC__" props={{ nome: 'host' }} fallback={<p>Carregando…</p>} />
|
|
8
|
+
</main>,
|
|
9
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { render } from 'solid-js/web'
|
|
2
|
+
import { Remote } from 'vitral/solid'
|
|
3
|
+
|
|
4
|
+
render(
|
|
5
|
+
() => (
|
|
6
|
+
<main>
|
|
7
|
+
<h1>Host Solid</h1>
|
|
8
|
+
<Remote src="__SRC__" props={{ nome: 'host' }} fallback={<p>Carregando…</p>} />
|
|
9
|
+
</main>
|
|
10
|
+
),
|
|
11
|
+
document.getElementById('app'),
|
|
12
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { VitralRemote } from 'vitral/vue'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<main>
|
|
7
|
+
<h1>Host Vue</h1>
|
|
8
|
+
<VitralRemote src="__SRC__" :props="{ nome: 'host' }">
|
|
9
|
+
<template #fallback><p>Carregando…</p></template>
|
|
10
|
+
</VitralRemote>
|
|
11
|
+
</main>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
test('o remote monta e responde ao clique', async ({ page }) => {
|
|
4
|
+
await page.goto('/')
|
|
5
|
+
await expect(page.getByRole('heading', { name: 'Olá, dev' })).toBeVisible()
|
|
6
|
+
await page.getByRole('button').click()
|
|
7
|
+
await expect(page.getByRole('button')).toHaveText('Cliques: 1')
|
|
8
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>__NAME__</title>
|
|
6
|
+
<script type="module">
|
|
7
|
+
import 'vitral/auto'
|
|
8
|
+
</script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<!-- Só para desenvolver: monta o remote como um host faria. -->
|
|
12
|
+
<vitral-remote src="/vitral.json" props='{"nome":"dev"}'></vitral-remote>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Component, input, signal } from '@angular/core'
|
|
2
|
+
|
|
3
|
+
// O plugin vitral embrulha o export default: não precisa de adaptador.
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'app-remote',
|
|
6
|
+
template: `
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, {{ nome() }}</h2>
|
|
9
|
+
<button (click)="cliques.set(cliques() + 1)">Cliques: {{ cliques() }}</button>
|
|
10
|
+
</section>
|
|
11
|
+
`,
|
|
12
|
+
})
|
|
13
|
+
export default class App {
|
|
14
|
+
nome = input('mundo')
|
|
15
|
+
cliques = signal(0)
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"useDefineForClassFields": false,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"lib": ["ES2022", "DOM"]
|
|
10
|
+
},
|
|
11
|
+
"files": ["src/App.ts"],
|
|
12
|
+
"angularCompilerOptions": { "strictTemplates": true }
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LitElement, html } from 'lit'
|
|
2
|
+
|
|
3
|
+
// O plugin vitral registra a tag e embrulha a classe: não precisa de adaptador.
|
|
4
|
+
export default class App extends LitElement {
|
|
5
|
+
static properties = { nome: {}, cliques: { state: true } }
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super()
|
|
9
|
+
this.nome = 'mundo'
|
|
10
|
+
this.cliques = 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
return html`<section>
|
|
15
|
+
<h2>Olá, ${this.nome}</h2>
|
|
16
|
+
<button @click=${() => this.cliques++}>Cliques: ${this.cliques}</button>
|
|
17
|
+
</section>`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks'
|
|
2
|
+
|
|
3
|
+
// O plugin vitral embrulha o export default: não precisa de adaptador.
|
|
4
|
+
export default function App({ nome = 'mundo' }) {
|
|
5
|
+
const [cliques, setCliques] = useState(0)
|
|
6
|
+
return (
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, {nome}</h2>
|
|
9
|
+
<button onClick={() => setCliques(cliques + 1)}>Cliques: {cliques}</button>
|
|
10
|
+
</section>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
// O plugin vitral embrulha o export default: não precisa de adaptador.
|
|
4
|
+
export default function App({ nome = 'mundo' }) {
|
|
5
|
+
const [cliques, setCliques] = useState(0)
|
|
6
|
+
return (
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, {nome}</h2>
|
|
9
|
+
<button onClick={() => setCliques(cliques + 1)}>Cliques: {cliques}</button>
|
|
10
|
+
</section>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSignal } from 'solid-js'
|
|
2
|
+
|
|
3
|
+
// O plugin vitral embrulha o export default: não precisa de adaptador.
|
|
4
|
+
export default function App(props) {
|
|
5
|
+
const [cliques, setCliques] = createSignal(0)
|
|
6
|
+
return (
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, {props.nome ?? 'mundo'}</h2>
|
|
9
|
+
<button onClick={() => setCliques(cliques() + 1)}>Cliques: {cliques()}</button>
|
|
10
|
+
</section>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!-- O plugin vitral embrulha o export default: não precisa de adaptador. -->
|
|
2
|
+
<script>
|
|
3
|
+
let { nome = 'mundo' } = $props()
|
|
4
|
+
let cliques = $state(0)
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, {nome}</h2>
|
|
9
|
+
<button onclick={() => cliques++}>Cliques: {cliques}</button>
|
|
10
|
+
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { component, html } from 'vitral'
|
|
2
|
+
|
|
3
|
+
// Remote sem framework: component() já tem mount/update/unmount.
|
|
4
|
+
export default component({
|
|
5
|
+
state: { cliques: 0 },
|
|
6
|
+
render: ({ nome = 'mundo' }, state) => html`
|
|
7
|
+
<section>
|
|
8
|
+
<h2>Olá, ${nome}</h2>
|
|
9
|
+
<button onclick=${() => state.cliques++}>Cliques: ${state.cliques}</button>
|
|
10
|
+
</section>
|
|
11
|
+
`,
|
|
12
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- O plugin vitral embrulha o export default: não precisa de adaptador. -->
|
|
2
|
+
<script setup>
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
defineProps({ nome: { type: String, default: 'mundo' } })
|
|
6
|
+
const cliques = ref(0)
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<section>
|
|
11
|
+
<h2>Olá, {{ nome }}</h2>
|
|
12
|
+
<button @click="cliques++">Cliques: {{ cliques }}</button>
|
|
13
|
+
</section>
|
|
14
|
+
</template>
|
package/README.md
DELETED