chore: format code

This commit is contained in:
Ayo Ayco 2024-12-19 23:11:07 +01:00
parent a362adab90
commit c25c5e03d4
35 changed files with 1365 additions and 615 deletions

View file

@ -1,5 +1,5 @@
import globals from "globals"; import globals from 'globals'
import pluginJs from "@eslint/js"; import pluginJs from '@eslint/js'
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
export default [ export default [
@ -7,12 +7,12 @@ export default [
pluginJs.configs.recommended, pluginJs.configs.recommended,
{ {
ignores: [ ignores: [
"dist/*", 'dist/*',
".output/*", '.output/*',
".nitro/*", '.nitro/*',
"node-modules*", 'node-modules*',
"site/*", 'site/*',
"templates/*", 'templates/*',
], ],
}, },
]; ]

View file

@ -1,28 +1,28 @@
#!/usr/bin/env node #!/usr/bin/env node
import { consola } from "consola"; import { consola } from 'consola'
import { defineCommand } from "citty"; import { defineCommand } from 'citty'
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
function build() { function build() {
consola.start("Building project..."); consola.start('Building project...')
try { try {
execSync(`npx nitropack build`, { stdio: "inherit" }); execSync(`npx nitropack build`, { stdio: 'inherit' })
} catch (err) { } catch (err) {
consola.error(err); consola.error(err)
} }
} }
export default defineCommand({ export default defineCommand({
meta: { meta: {
name: "prepare", name: 'prepare',
description: "Builds the McFly project for production.", description: 'Builds the McFly project for production.',
}, },
async run() { async run() {
build() build()
}, },
}); })
export const exportedForTest = { export const exportedForTest = {
build, build,

View file

@ -1,22 +1,22 @@
#!/usr/bin/env node #!/usr/bin/env node
import { consola } from "consola"; import { consola } from 'consola'
import { defineCommand } from "citty"; import { defineCommand } from 'citty'
function generate() { function generate() {
consola.box("Generate a McFly building block (In-progress)"); consola.box('Generate a McFly building block (In-progress)')
} }
export default defineCommand({ export default defineCommand({
meta: { meta: {
name: "prepare", name: 'prepare',
description: "Generates building blocks for a McFly app.", description: 'Generates building blocks for a McFly app.',
}, },
run() { run() {
generate(); generate()
}, },
}); })
export const exportedForTest = { export const exportedForTest = {
generate, generate,
}; }

View file

@ -1,43 +1,43 @@
#!/usr/bin/env node #!/usr/bin/env node
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
import { consola } from "consola"; import { consola } from 'consola'
import { defineCommand } from "citty"; import { defineCommand } from 'citty'
function createNew(args) { function createNew(args) {
const directory = args.dir || args._dir; const directory = args.dir || args._dir
const command = directory const command = directory
? `npm create mcfly@latest ${directory}` ? `npm create mcfly@latest ${directory}`
: "npm create mcfly@latest"; : 'npm create mcfly@latest'
try { try {
execSync(command, { stdio: "inherit" }); execSync(command, { stdio: 'inherit' })
} catch (e) { } catch (e) {
consola.error(e); consola.error(e)
} }
} }
export default defineCommand({ export default defineCommand({
meta: { meta: {
name: "prepare", name: 'prepare',
description: "Creates a new McFly project.", description: 'Creates a new McFly project.',
}, },
args: { args: {
dir: { dir: {
type: "string", type: 'string',
description: "project root directory", description: 'project root directory',
required: false, required: false,
}, },
_dir: { _dir: {
type: "positional", type: 'positional',
description: "project root directory (prefer using `--dir`)", description: 'project root directory (prefer using `--dir`)',
required: false, required: false,
}, },
}, },
async run({ args }) { async run({ args }) {
createNew(args) createNew(args)
}, },
}); })
export const exportedForTest = { export const exportedForTest = {
createNew createNew,
} }

View file

@ -1,38 +1,38 @@
#!/usr/bin/env node #!/usr/bin/env node
import { consola } from "consola"; import { consola } from 'consola'
import { defineCommand } from "citty"; import { defineCommand } from 'citty'
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
function prepare() { function prepare() {
consola.start("Preparing McFly workspace..."); consola.start('Preparing McFly workspace...')
let err; let err
try { try {
execSync("npx nitropack prepare", { stdio: "inherit" }); execSync('npx nitropack prepare', { stdio: 'inherit' })
} catch (e) { } catch (e) {
consola.error(e); consola.error(e)
err = e; err = e
} }
if (err) { if (err) {
consola.fail( consola.fail(
"McFly workspace preparation failed. Please make sure dependencies are installed.\n" 'McFly workspace preparation failed. Please make sure dependencies are installed.\n'
); )
} else consola.success("Done\n"); } else consola.success('Done\n')
} }
export default defineCommand({ export default defineCommand({
meta: { meta: {
name: "prepare", name: 'prepare',
description: "Prepares the McFly workspace.", description: 'Prepares the McFly workspace.',
}, },
run() { run() {
prepare(); prepare()
}, },
}); })
export const exportedForTest = { export const exportedForTest = {
prepare prepare,
} }

View file

@ -1,46 +1,46 @@
#!/usr/bin/env node #!/usr/bin/env node
import { consola } from "consola"; import { consola } from 'consola'
import { colorize } from "consola/utils"; import { colorize } from 'consola/utils'
import { defineCommand } from "citty"; import { defineCommand } from 'citty'
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
import { createRequire } from "node:module"; import { createRequire } from 'node:module'
async function printInfo() { async function printInfo() {
try { try {
const _require = createRequire(import.meta.url); const _require = createRequire(import.meta.url)
const mcflyPkg = await _require("@mcflyjs/core/package.json"); const mcflyPkg = await _require('@mcflyjs/core/package.json')
const mcflyPkgVersion = `McFly ${colorize("bold", mcflyPkg.version)}`; const mcflyPkgVersion = `McFly ${colorize('bold', mcflyPkg.version)}`
const nitroPkg = await _require("nitropack/package.json"); const nitroPkg = await _require('nitropack/package.json')
const nitroPkgVersion = `Nitro ${nitroPkg.version}`; const nitroPkgVersion = `Nitro ${nitroPkg.version}`
consola.log( consola.log(
`${colorize("blue", mcflyPkgVersion)} ${colorize("dim", nitroPkgVersion)}` `${colorize('blue', mcflyPkgVersion)} ${colorize('dim', nitroPkgVersion)}`
); )
} catch (e) { } catch (e) {
consola.error(e); consola.error(e)
} }
} }
function serve() { function serve() {
try { try {
execSync(`npx nitropack dev`, { stdio: "inherit" }); execSync(`npx nitropack dev`, { stdio: 'inherit' })
} catch (e) { } catch (e) {
consola.error(e); consola.error(e)
} }
} }
export default defineCommand({ export default defineCommand({
meta: { meta: {
name: "prepare", name: 'prepare',
description: "Runs the dev server.", description: 'Runs the dev server.',
}, },
async run() { async run() {
await printInfo(); await printInfo()
serve(); serve()
}, },
}); })
export const exportedForTest = { export const exportedForTest = {
serve, serve,
printInfo, printInfo,
}; }

View file

@ -1,23 +1,23 @@
#!/usr/bin/env node #!/usr/bin/env node
import { defineCommand, runMain } from "citty"; import { defineCommand, runMain } from 'citty'
const main = defineCommand({ const main = defineCommand({
meta: { meta: {
name: "mcfly", name: 'mcfly',
description: "McFly CLI", description: 'McFly CLI',
}, },
subCommands: { subCommands: {
new: () => import("./commands/new.mjs").then((r) => r.default), new: () => import('./commands/new.mjs').then((r) => r.default),
serve: () => import("./commands/serve.mjs").then((r) => r.default), serve: () => import('./commands/serve.mjs').then((r) => r.default),
build: () => import("./commands/build.mjs").then((r) => r.default), build: () => import('./commands/build.mjs').then((r) => r.default),
prepare: () => import("./commands/prepare.mjs").then((r) => r.default), prepare: () => import('./commands/prepare.mjs').then((r) => r.default),
generate: () => import("./commands/generate.mjs").then((r) => r.default), generate: () => import('./commands/generate.mjs').then((r) => r.default),
g: () => import("./commands/generate.mjs").then((r) => r.default), g: () => import('./commands/generate.mjs').then((r) => r.default),
}, },
}); })
runMain(main); runMain(main)
export const exportedForTest = { export const exportedForTest = {
main, main,
}; }

View file

@ -1,46 +1,46 @@
import consola from "consola"; import consola from 'consola'
import { vi, expect, test } from "vitest"; import { vi, expect, test } from 'vitest'
import { exportedForTest } from "../commands/build.mjs"; import { exportedForTest } from '../commands/build.mjs'
const testFn = exportedForTest.build; const testFn = exportedForTest.build
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => {
return { return {
execSync: vi.fn(), execSync: vi.fn(),
}; }
}); })
vi.mock("node:child_process", () => { vi.mock('node:child_process', () => {
return { return {
execSync: mocks.execSync, execSync: mocks.execSync,
}; }
}); })
test("start build with message", () => { test('start build with message', () => {
const message = "Building project..."; const message = 'Building project...'
const spy = vi.spyOn(consola, "start"); const spy = vi.spyOn(consola, 'start')
testFn(); testFn()
expect(spy).toHaveBeenCalledWith(message); expect(spy).toHaveBeenCalledWith(message)
}); })
test("execute nitropack build", () => { test('execute nitropack build', () => {
const command = "npx nitropack build"; const command = 'npx nitropack build'
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn(); testFn()
expect(mocks.execSync).toHaveBeenCalledWith(command, param); expect(mocks.execSync).toHaveBeenCalledWith(command, param)
}); })
test("catch error", () => { test('catch error', () => {
const spy = vi.spyOn(consola, "error"); const spy = vi.spyOn(consola, 'error')
mocks.execSync.mockImplementationOnce(() => { mocks.execSync.mockImplementationOnce(() => {
throw new Error("hey"); throw new Error('hey')
}); })
testFn(); testFn()
expect(spy).toHaveBeenCalledWith(new Error("hey")); expect(spy).toHaveBeenCalledWith(new Error('hey'))
}); })

View file

@ -1,15 +1,15 @@
import { expect, test, vi } from "vitest"; import { expect, test, vi } from 'vitest'
import { exportedForTest } from "../commands/generate.mjs"; import { exportedForTest } from '../commands/generate.mjs'
import consola from "consola"; import consola from 'consola'
const testFn = exportedForTest.generate; const testFn = exportedForTest.generate
test("show box message in-progress", () => { test('show box message in-progress', () => {
const spy = vi.spyOn(consola, "box"); const spy = vi.spyOn(consola, 'box')
testFn(); testFn()
const arg = spy.mock.calls[0][0]; const arg = spy.mock.calls[0][0]
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled()
expect(arg).toContain("In-progress"); expect(arg).toContain('In-progress')
}); })

View file

@ -1,12 +1,12 @@
import { test } from "vitest"; import { test } from 'vitest'
import { exportedForTest } from ".."; import { exportedForTest } from '..'
import { expect } from "vitest"; import { expect } from 'vitest'
const testObj = exportedForTest.main; const testObj = exportedForTest.main
test("should have correct subcommands", () => { test('should have correct subcommands', () => {
Object.keys(testObj.subCommands).forEach((key) => { Object.keys(testObj.subCommands).forEach((key) => {
expect(testObj.subCommands[key]).toBeTypeOf("function"); expect(testObj.subCommands[key]).toBeTypeOf('function')
expect(testObj.subCommands[key].name).toBe(key); expect(testObj.subCommands[key].name).toBe(key)
}); })
}); })

View file

@ -1,79 +1,79 @@
import { expect, test, vi } from "vitest"; import { expect, test, vi } from 'vitest'
import { exportedForTest } from "../commands/new.mjs"; import { exportedForTest } from '../commands/new.mjs'
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
import consola from "consola"; import consola from 'consola'
const testFn = exportedForTest.createNew; const testFn = exportedForTest.createNew
const baseCommand = `npm create mcfly@latest`; const baseCommand = `npm create mcfly@latest`
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => {
return { return {
execSync: vi.fn(), execSync: vi.fn(),
}; }
}); })
vi.mock("node:child_process", () => { vi.mock('node:child_process', () => {
return { return {
execSync: mocks.execSync, execSync: mocks.execSync,
}; }
}); })
test("execute create mcfly", () => { test('execute create mcfly', () => {
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn({ dir: undefined }); testFn({ dir: undefined })
expect(execSync).toHaveBeenCalledWith(baseCommand, param); expect(execSync).toHaveBeenCalledWith(baseCommand, param)
}); })
test("execute create mcfly with no dir", () => { test('execute create mcfly with no dir', () => {
const dir = "fake-dir"; const dir = 'fake-dir'
const command = `${baseCommand} ${dir}`; const command = `${baseCommand} ${dir}`
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn({ dir: undefined }); testFn({ dir: undefined })
expect(execSync).not.toHaveBeenCalledWith(command, param); expect(execSync).not.toHaveBeenCalledWith(command, param)
}); })
test("execute create mcfly with dir", () => { test('execute create mcfly with dir', () => {
const dir = "fake-dir"; const dir = 'fake-dir'
const command = `${baseCommand} ${dir}`; const command = `${baseCommand} ${dir}`
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn({ dir }); testFn({ dir })
expect(execSync).toHaveBeenCalledWith(command, param); expect(execSync).toHaveBeenCalledWith(command, param)
}); })
test("execute create mcfly with _dir", () => { test('execute create mcfly with _dir', () => {
const dir = "fake-dir"; const dir = 'fake-dir'
const command = `${baseCommand} ${dir}`; const command = `${baseCommand} ${dir}`
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn({ _dir: dir }); testFn({ _dir: dir })
expect(execSync).toHaveBeenCalledWith(command, param); expect(execSync).toHaveBeenCalledWith(command, param)
}); })
test("execute create mcfly with dir preferred over _dir", () => { test('execute create mcfly with dir preferred over _dir', () => {
const dir = "preferred-dir"; const dir = 'preferred-dir'
const command = `${baseCommand} ${dir}`; const command = `${baseCommand} ${dir}`
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn({ dir: dir, _dir: "not-preferred" }); testFn({ dir: dir, _dir: 'not-preferred' })
expect(execSync).toHaveBeenCalledWith(command, param); expect(execSync).toHaveBeenCalledWith(command, param)
}); })
test("catch error", () => { test('catch error', () => {
const dir = "fake-dir"; const dir = 'fake-dir'
const spy = vi.spyOn(consola, "error"); const spy = vi.spyOn(consola, 'error')
mocks.execSync.mockImplementationOnce(() => { mocks.execSync.mockImplementationOnce(() => {
throw new Error("hey"); throw new Error('hey')
}); })
testFn({ dir }); testFn({ dir })
expect(spy).toHaveBeenCalledWith(new Error("hey")); expect(spy).toHaveBeenCalledWith(new Error('hey'))
}); })

View file

@ -1,50 +1,50 @@
import { test, expect, vi } from "vitest"; import { test, expect, vi } from 'vitest'
import { exportedForTest } from "../commands/prepare.mjs"; import { exportedForTest } from '../commands/prepare.mjs'
import consola from "consola"; import consola from 'consola'
import { execSync } from "node:child_process"; import { execSync } from 'node:child_process'
const testFn = exportedForTest.prepare; const testFn = exportedForTest.prepare
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => {
return { return {
execSync: vi.fn(), execSync: vi.fn(),
}; }
}); })
vi.mock("node:child_process", () => { vi.mock('node:child_process', () => {
return { return {
execSync: mocks.execSync, execSync: mocks.execSync,
}; }
}); })
test("start prepare script", () => { test('start prepare script', () => {
const spy = vi.spyOn(consola, "start"); const spy = vi.spyOn(consola, 'start')
testFn(); testFn()
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled()
}); })
test("execute nitropack prepare", () => { test('execute nitropack prepare', () => {
const successSpy = vi.spyOn(consola, "success"); const successSpy = vi.spyOn(consola, 'success')
const command = "npx nitropack prepare"; const command = 'npx nitropack prepare'
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn(); testFn()
expect(execSync).toHaveBeenCalledWith(command, param); expect(execSync).toHaveBeenCalledWith(command, param)
expect(successSpy).toHaveBeenCalled(); expect(successSpy).toHaveBeenCalled()
}); })
test("catch error", () => { test('catch error', () => {
const errSpy = vi.spyOn(consola, "error"); const errSpy = vi.spyOn(consola, 'error')
const failSpy = vi.spyOn(consola, "fail"); const failSpy = vi.spyOn(consola, 'fail')
mocks.execSync.mockImplementationOnce(() => { mocks.execSync.mockImplementationOnce(() => {
throw new Error(); throw new Error()
}); })
testFn(); testFn()
expect(errSpy).toHaveBeenCalled(); expect(errSpy).toHaveBeenCalled()
expect(failSpy).toHaveBeenCalled(); expect(failSpy).toHaveBeenCalled()
}); })

View file

@ -1,83 +1,83 @@
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from 'vitest'
import { exportedForTest } from "../commands/serve.mjs"; import { exportedForTest } from '../commands/serve.mjs'
import consola from "consola"; import consola from 'consola'
describe("FUNCTION: serve()", () => { describe('FUNCTION: serve()', () => {
const testFn = exportedForTest.serve; const testFn = exportedForTest.serve
const mocks = vi.hoisted(() => { const mocks = vi.hoisted(() => {
return { return {
execSync: vi.fn(), execSync: vi.fn(),
}; }
}); })
vi.mock("node:child_process", () => { vi.mock('node:child_process', () => {
return { return {
execSync: mocks.execSync, execSync: mocks.execSync,
}; }
}); })
test("execute nitropack serve", async () => { test('execute nitropack serve', async () => {
const command = `npx nitropack dev`; const command = `npx nitropack dev`
const param = { stdio: "inherit" }; const param = { stdio: 'inherit' }
testFn(); testFn()
expect(mocks.execSync).toHaveBeenCalledWith(command, param); expect(mocks.execSync).toHaveBeenCalledWith(command, param)
}); })
test("catch error", () => { test('catch error', () => {
const spy = vi.spyOn(consola, "error"); const spy = vi.spyOn(consola, 'error')
mocks.execSync.mockImplementationOnce(() => { mocks.execSync.mockImplementationOnce(() => {
throw new Error("hey"); throw new Error('hey')
}); })
testFn(); testFn()
expect(spy).toHaveBeenCalledWith(new Error("hey")); expect(spy).toHaveBeenCalledWith(new Error('hey'))
}); })
}); })
describe("FUNCTION: printInfo()", () => { describe('FUNCTION: printInfo()', () => {
const testFn = exportedForTest.printInfo; const testFn = exportedForTest.printInfo
const createRequireMocks = vi.hoisted(() => { const createRequireMocks = vi.hoisted(() => {
return { return {
createRequire: vi.fn(), createRequire: vi.fn(),
}; }
}); })
vi.mock("node:module", () => { vi.mock('node:module', () => {
return { return {
createRequire: createRequireMocks.createRequire, createRequire: createRequireMocks.createRequire,
}; }
}); })
test("log mcfly and nitro versions", async () => { test('log mcfly and nitro versions', async () => {
const spy = vi.spyOn(consola, "log"); const spy = vi.spyOn(consola, 'log')
const fakeMessage = "McFly -1.0.0 Nitro -1.0.0"; const fakeMessage = 'McFly -1.0.0 Nitro -1.0.0'
createRequireMocks.createRequire.mockImplementationOnce(() => { createRequireMocks.createRequire.mockImplementationOnce(() => {
return () => { return () => {
return { return {
version: "-1.0.0", version: '-1.0.0',
}; }
}; }
}); })
await testFn(); await testFn()
expect(spy.mock.calls[0][0]).toContain("McFly"); expect(spy.mock.calls[0][0]).toContain('McFly')
expect(spy.mock.calls[0][0]).toContain("Nitro"); expect(spy.mock.calls[0][0]).toContain('Nitro')
expect(spy).toHaveBeenCalledWith(fakeMessage); expect(spy).toHaveBeenCalledWith(fakeMessage)
}); })
test("catch error", async () => { test('catch error', async () => {
createRequireMocks.createRequire.mockImplementationOnce(() => { createRequireMocks.createRequire.mockImplementationOnce(() => {
throw new Error("error"); throw new Error('error')
}); })
const spy = vi.spyOn(consola, "error"); const spy = vi.spyOn(consola, 'error')
await testFn(); await testFn()
expect(spy).toHaveBeenCalledWith(new Error("error")); expect(spy).toHaveBeenCalledWith(new Error('error'))
}); })
}); })

View file

@ -1,12 +1,12 @@
import { coverageConfigDefaults, defineConfig } from "vitest/config"; import { coverageConfigDefaults, defineConfig } from 'vitest/config'
export default defineConfig({ export default defineConfig({
test: { test: {
coverage: { coverage: {
enabled: true, enabled: true,
provider: "v8", provider: 'v8',
reporter: ["html", "text"], reporter: ['html', 'text'],
exclude: ["html/**", ...coverageConfigDefaults.exclude], exclude: ['html/**', ...coverageConfigDefaults.exclude],
}, },
}, },
}); })

View file

@ -9,33 +9,33 @@
export default function () { export default function () {
return { return {
framework: { framework: {
name: "McFly", name: 'McFly',
}, },
compatibilityDate: "2024-12-08", compatibilityDate: '2024-12-08',
devServer: { devServer: {
watch: ["./src/pages", "./src/components"], watch: ['./src/pages', './src/components'],
}, },
serverAssets: [ serverAssets: [
{ {
baseName: "pages", baseName: 'pages',
dir: "./src/pages", dir: './src/pages',
}, },
{ {
baseName: "components", baseName: 'components',
dir: "./src/components", dir: './src/components',
}, },
], ],
imports: { imports: {
presets: [ presets: [
{ {
from: "web-component-base", from: 'web-component-base',
imports: ["WebComponent", "html", "attachEffect"], imports: ['WebComponent', 'html', 'attachEffect'],
}, },
{ {
from: "@mcflyjs/core", from: '@mcflyjs/core',
imports: ["useMcFlyRoute", "defineMcFlyConfig"], imports: ['useMcFlyRoute', 'defineMcFlyConfig'],
}, },
], ],
}, },
}; }
} }

View file

@ -10,5 +10,5 @@
* @returns {function(): McFlyConfig} * @returns {function(): McFlyConfig}
*/ */
export function defineMcFlyConfig(config) { export function defineMcFlyConfig(config) {
return () => config; return () => config
} }

View file

@ -1,6 +1,6 @@
import { eventHandler } from "h3"; import { eventHandler } from 'h3'
import { ELEMENT_NODE, parse, render, renderSync, walkSync } from "ultrahtml"; import { ELEMENT_NODE, parse, render, renderSync, walkSync } from 'ultrahtml'
import { parseScript } from "esprima"; import { parseScript } from 'esprima'
/** /**
* @typedef {import('./define-config.js').McFlyConfig} Config * @typedef {import('./define-config.js').McFlyConfig} Config
@ -21,36 +21,36 @@ import { parseScript } from "esprima";
*/ */
export function useMcFlyRoute({ config, storage }) { export function useMcFlyRoute({ config, storage }) {
return eventHandler(async (event) => { return eventHandler(async (event) => {
const { path } = event; const { path } = event
const { components: componentType } = config(); const { components: componentType } = config()
let html = await getHtml(path, storage); let html = await getHtml(path, storage)
if (html) { if (html) {
const transforms = [doSetUp, deleteServerScripts]; const transforms = [doSetUp, deleteServerScripts]
for (const transform of transforms) { for (const transform of transforms) {
html = transform(html.toString()); html = transform(html.toString())
} }
html = await useFragments(html.toString(), storage); html = await useFragments(html.toString(), storage)
if (!!componentType && !!html) { if (!!componentType && !!html) {
html = await insertRegistry(html.toString(), componentType, storage); html = await insertRegistry(html.toString(), componentType, storage)
} }
} }
return ( return (
html ?? html ??
new Response( new Response(
"😱 ERROR 404: Not found. You can put a 404.html on the ./src/pages directory to customize this error page.", '😱 ERROR 404: Not found. You can put a 404.html on the ./src/pages directory to customize this error page.',
{ status: 404 } { status: 404 }
) )
); )
}); })
} }
function getPurePath(path) { function getPurePath(path) {
return path.split("?")[0]; return path.split('?')[0]
} }
/** /**
@ -60,17 +60,17 @@ function getPurePath(path) {
* @returns {Promise<StorageValue>} * @returns {Promise<StorageValue>}
*/ */
async function getHtml(path, storage) { async function getHtml(path, storage) {
const purePath = getPurePath(path); const purePath = getPurePath(path)
const rawPath = const rawPath =
purePath[purePath.length - 1] === "/" ? purePath.slice(0, -1) : purePath; purePath[purePath.length - 1] === '/' ? purePath.slice(0, -1) : purePath
const filename = rawPath === "" ? "/index.html" : `${rawPath}.html`; const filename = rawPath === '' ? '/index.html' : `${rawPath}.html`
const fallback = getPath(rawPath + "/index.html"); const fallback = getPath(rawPath + '/index.html')
const filePath = getPath(filename); const filePath = getPath(filename)
let html = await storage.getItem(filePath); let html = await storage.getItem(filePath)
if (!html) html = await storage.getItem(fallback); if (!html) html = await storage.getItem(fallback)
if (!html) html = await storage.getItem(getPath("/404.html")); if (!html) html = await storage.getItem(getPath('/404.html'))
return html; return html
} }
/** /**
@ -79,7 +79,7 @@ async function getHtml(path, storage) {
* @returns {string} * @returns {string}
*/ */
function getPath(filename) { function getPath(filename) {
return `assets:pages${filename}`; return `assets:pages${filename}`
} }
/** /**
@ -90,21 +90,21 @@ function getPath(filename) {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async function insertRegistry(html, type, storage) { async function insertRegistry(html, type, storage) {
const ast = parse(html); const ast = parse(html)
const componentFiles = await getFiles(type, storage); const componentFiles = await getFiles(type, storage)
const availableComponents = componentFiles.map((key) => const availableComponents = componentFiles.map((key) =>
key.replace(`.${type}`, "") key.replace(`.${type}`, '')
); )
const usedCustomElements = []; const usedCustomElements = []
walkSync(ast, (node) => { walkSync(ast, (node) => {
const usedElement = availableComponents.find((name) => name === node.name); const usedElement = availableComponents.find((name) => name === node.name)
if (node.type === ELEMENT_NODE && !!usedElement) { if (node.type === ELEMENT_NODE && !!usedElement) {
usedCustomElements.push(usedElement); usedCustomElements.push(usedElement)
} }
}); })
// insert registry script to head // insert registry script to head
if (usedCustomElements.length > 0) { if (usedCustomElements.length > 0) {
@ -112,15 +112,15 @@ async function insertRegistry(html, type, storage) {
usedCustomElements, usedCustomElements,
type, type,
storage storage
); )
walkSync(ast, (node) => { walkSync(ast, (node) => {
if (node.type === ELEMENT_NODE && node.name === "head") { if (node.type === ELEMENT_NODE && node.name === 'head') {
node.children.push(parse(registryScript)); node.children.push(parse(registryScript))
} }
}); })
} }
return render(ast); return render(ast)
} }
/** /**
@ -131,41 +131,41 @@ async function insertRegistry(html, type, storage) {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async function buildRegistry(usedCustomElements, type, storage) { async function buildRegistry(usedCustomElements, type, storage) {
let registryScript = `<script type='module'>`; let registryScript = `<script type='module'>`
let isBaseClassImported = false; let isBaseClassImported = false
let classesImported = []; let classesImported = []
for (const name of usedCustomElements) { for (const name of usedCustomElements) {
const content = await storage.getItem(`assets:components:${name}.${type}`); const content = await storage.getItem(`assets:components:${name}.${type}`)
if (!content) continue; if (!content) continue
const evalStore = eval( const evalStore = eval(
`class WebComponent {}; class HTMLElement {}; (${content.toString()})` `class WebComponent {}; class HTMLElement {}; (${content.toString()})`
); )
if (isConstructor(evalStore)) { if (isConstructor(evalStore)) {
const className = new evalStore().constructor.name; const className = new evalStore().constructor.name
if (!classesImported.includes(className)) { if (!classesImported.includes(className)) {
if ( if (
!isBaseClassImported && !isBaseClassImported &&
content.toString().includes("extends WebComponent") content.toString().includes('extends WebComponent')
) { ) {
const baseClassImport = `import { WebComponent, html, attachEffect } from "https://unpkg.com/web-component-base@2.0.6/index.js";`; const baseClassImport = `import { WebComponent, html, attachEffect } from "https://unpkg.com/web-component-base@2.0.6/index.js";`
registryScript += baseClassImport; registryScript += baseClassImport
isBaseClassImported = true; isBaseClassImported = true
} }
registryScript += content; registryScript += content
registryScript += `customElements.define("${name}", ${className});`; registryScript += `customElements.define("${name}", ${className});`
classesImported.push(className); classesImported.push(className)
} }
} }
} }
registryScript += "</script>"; registryScript += '</script>'
return registryScript; return registryScript
} }
/** /**
@ -175,13 +175,13 @@ async function buildRegistry(usedCustomElements, type, storage) {
*/ */
function isConstructor(f) { function isConstructor(f) {
try { try {
new f(); new f()
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
} catch (err) { } catch (err) {
// TODO: verify err is the expected error and then // TODO: verify err is the expected error and then
return false; return false
} }
return true; return true
} }
/** /**
@ -190,56 +190,56 @@ function isConstructor(f) {
* @returns {string} * @returns {string}
*/ */
function doSetUp(html) { function doSetUp(html) {
const ast = parse(html); const ast = parse(html)
const serverScripts = []; const serverScripts = []
walkSync(ast, (node) => { walkSync(ast, (node) => {
const { attributes } = node; const { attributes } = node
const attributeKeys = Object.keys(attributes ?? {}); const attributeKeys = Object.keys(attributes ?? {})
const isServerScript = attributeKeys.some((key) => key.includes("server:")); const isServerScript = attributeKeys.some((key) => key.includes('server:'))
if ( if (
node.type === ELEMENT_NODE && node.type === ELEMENT_NODE &&
node.name === "script" && node.name === 'script' &&
isServerScript isServerScript
) { ) {
const scripts = node.children.map((child) => child.value); const scripts = node.children.map((child) => child.value)
const script = cleanScript(scripts); const script = cleanScript(scripts)
serverScripts.push(script); serverScripts.push(script)
} }
}); })
const setupMap = {}; const setupMap = {}
serverScripts.forEach((script) => { serverScripts.forEach((script) => {
const { body } = parseScript(script); const { body } = parseScript(script)
const keys = body const keys = body
.filter((n) => n.type === "VariableDeclaration") .filter((n) => n.type === 'VariableDeclaration')
.map((n) => n["declarations"][0].id.name); .map((n) => n['declarations'][0].id.name)
const constructor = `(function(){}.constructor)(\`${script}; return {${keys.join( const constructor = `(function(){}.constructor)(\`${script}; return {${keys.join(
"," ','
)}}\`);`; )}}\`);`
const evalStore = eval(constructor); const evalStore = eval(constructor)
Object.assign(setupMap, new evalStore()); Object.assign(setupMap, new evalStore())
}); })
const regex = /{{(.*?)}}/g; const regex = /{{(.*?)}}/g
let match; let match
while ((match = regex.exec(html))) { while ((match = regex.exec(html))) {
let [key, value] = match; let [key, value] = match
value = value.replace(/\s/g, ""); value = value.replace(/\s/g, '')
// nested objects // nested objects
const keys = value.split("."); const keys = value.split('.')
let finalValue = ""; let finalValue = ''
let setupCopy = setupMap; let setupCopy = setupMap
keys.forEach((i) => { keys.forEach((i) => {
finalValue = setupCopy[i]; finalValue = setupCopy[i]
setupCopy = finalValue; setupCopy = finalValue
}); })
html = html.replace(key, finalValue); html = html.replace(key, finalValue)
} }
return html; return html
} }
/** /**
@ -248,17 +248,17 @@ function doSetUp(html) {
* @returns {string} * @returns {string}
*/ */
function deleteServerScripts(html) { function deleteServerScripts(html) {
const ast = parse(html); const ast = parse(html)
walkSync(ast, (node) => { walkSync(ast, (node) => {
const { attributes } = node; const { attributes } = node
const attributeKeys = Object.keys(attributes ?? {}); const attributeKeys = Object.keys(attributes ?? {})
const isServerScript = attributeKeys.some((key) => key.includes("server:")); const isServerScript = attributeKeys.some((key) => key.includes('server:'))
if (isServerScript && !!node.parent) { if (isServerScript && !!node.parent) {
node.parent.children.splice(node.parent.children.indexOf(node), 1); node.parent.children.splice(node.parent.children.indexOf(node), 1)
} }
}); })
return renderSync(ast); return renderSync(ast)
} }
/** /**
@ -267,11 +267,11 @@ function deleteServerScripts(html) {
* @returns {string} * @returns {string}
*/ */
function cleanScript(scripts) { function cleanScript(scripts) {
let script = scripts.map((s) => s.trim()).join(" "); let script = scripts.map((s) => s.trim()).join(' ')
script = removeComments(script); script = removeComments(script)
return script.replace(/\n/g, "").replace(/\s+/g, " "); return script.replace(/\n/g, '').replace(/\s+/g, ' ')
} }
/** /**
@ -281,11 +281,11 @@ function cleanScript(scripts) {
*/ */
function isComment(node) { function isComment(node) {
return ( return (
node.type === "Line" || node.type === 'Line' ||
node.type === "Block" || node.type === 'Block' ||
node.type === "BlockComment" || node.type === 'BlockComment' ||
node.type === "LineComment" node.type === 'LineComment'
); )
} }
/** /**
@ -294,25 +294,25 @@ function isComment(node) {
* @returns {string} * @returns {string}
*/ */
function removeComments(script) { function removeComments(script) {
const entries = []; const entries = []
parseScript(script, { comment: true }, function (node, meta) { parseScript(script, { comment: true }, function (node, meta) {
if (isComment(node)) { if (isComment(node)) {
entries.push({ entries.push({
start: meta.start.offset, start: meta.start.offset,
end: meta.end.offset, end: meta.end.offset,
}); })
} }
}); })
entries entries
.sort((a, b) => { .sort((a, b) => {
return b.end - a.end; return b.end - a.end
}) })
.forEach((n) => { .forEach((n) => {
script = script.slice(0, n.start) + script.slice(n.end); script = script.slice(0, n.start) + script.slice(n.end)
}); })
return script; return script
} }
/** /**
@ -322,44 +322,44 @@ function removeComments(script) {
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async function useFragments(html, storage) { async function useFragments(html, storage) {
const fragmentFiles = await getFiles("html", storage); const fragmentFiles = await getFiles('html', storage)
const availableFragments = fragmentFiles.reduce((acc, key) => { const availableFragments = fragmentFiles.reduce((acc, key) => {
return { return {
...acc, ...acc,
[key.replace(".html", "")]: "", [key.replace('.html', '')]: '',
}; }
}, {}); }, {})
const ast = parse(html); const ast = parse(html)
for (const key in availableFragments) { for (const key in availableFragments) {
/** /**
* @type string | null * @type string | null
*/ */
let text = await storage.getItem("assets:components:" + key + ".html"); let text = await storage.getItem('assets:components:' + key + '.html')
if (!text) continue; if (!text) continue
availableFragments[key] = text.replace(/\n/g, "").replace(/\s+/g, " "); availableFragments[key] = text.replace(/\n/g, '').replace(/\s+/g, ' ')
} }
walkSync(ast, (node) => { walkSync(ast, (node) => {
const selector = Object.keys(availableFragments).find( const selector = Object.keys(availableFragments).find(
(name) => name === node.name (name) => name === node.name
); )
if (node.type === ELEMENT_NODE && !!selector) { if (node.type === ELEMENT_NODE && !!selector) {
const index = node.parent.children.indexOf(node); const index = node.parent.children.indexOf(node)
/** /**
* @type {HtmlNode} * @type {HtmlNode}
*/ */
const fragmentNode = parse(availableFragments[selector]); const fragmentNode = parse(availableFragments[selector])
replaceSlots(fragmentNode, node); replaceSlots(fragmentNode, node)
node.parent.children[index] = fragmentNode; node.parent.children[index] = fragmentNode
} }
}); })
return render(ast); return render(ast)
} }
/** /**
@ -369,32 +369,32 @@ async function useFragments(html, storage) {
* @returns {void} * @returns {void}
*/ */
function replaceSlots(fragmentNode, node) { function replaceSlots(fragmentNode, node) {
let slotted = []; let slotted = []
const containsAll = (arr, target) => target.every((v) => arr.includes(v)); const containsAll = (arr, target) => target.every((v) => arr.includes(v))
walkSync(fragmentNode, (n) => { walkSync(fragmentNode, (n) => {
if (n.type === ELEMENT_NODE && n.name === "slot") { if (n.type === ELEMENT_NODE && n.name === 'slot') {
// find node child with same name attribute // find node child with same name attribute
const currentSlotName = n.attributes?.["name"] ?? null; const currentSlotName = n.attributes?.['name'] ?? null
let nodeChildren = []; let nodeChildren = []
if (currentSlotName === null) { if (currentSlotName === null) {
nodeChildren = node.children.filter( nodeChildren = node.children.filter(
(child) => !child.attributes?.["slot"] (child) => !child.attributes?.['slot']
); )
} else { } else {
nodeChildren = node.children.filter((child) => { nodeChildren = node.children.filter((child) => {
const childSlotName = child.attributes?.["slot"]; const childSlotName = child.attributes?.['slot']
return childSlotName === currentSlotName; return childSlotName === currentSlotName
}); })
} }
if (nodeChildren.length > 0 && !containsAll(slotted, nodeChildren)) { if (nodeChildren.length > 0 && !containsAll(slotted, nodeChildren)) {
slotted = [...slotted, ...nodeChildren]; slotted = [...slotted, ...nodeChildren]
const index = n.parent.children.indexOf(n); const index = n.parent.children.indexOf(n)
n.parent.children.splice(index, 1, ...nodeChildren); n.parent.children.splice(index, 1, ...nodeChildren)
} }
} }
}); })
} }
/** /**
@ -404,7 +404,7 @@ function replaceSlots(fragmentNode, node) {
* @returns {Promise<string[]>} * @returns {Promise<string[]>}
*/ */
async function getFiles(type, storage) { async function getFiles(type, storage) {
return (await storage.getKeys("assets:components")) return (await storage.getKeys('assets:components'))
.map((key) => key.replace("assets:components:", "")) .map((key) => key.replace('assets:components:', ''))
.filter((key) => key.includes(type)); .filter((key) => key.includes(type))
} }

View file

@ -1,2 +1,2 @@
export { defineMcFlyConfig } from "./define-config.js"; export { defineMcFlyConfig } from './define-config.js'
export { useMcFlyRoute } from "./event-handler.js"; export { useMcFlyRoute } from './event-handler.js'

View file

@ -1,12 +1,12 @@
#!/usr/bin/env node #!/usr/bin/env node
import { consola } from "consola"; import { consola } from 'consola'
import { colorize } from "consola/utils"; import { colorize } from 'consola/utils'
import { downloadTemplate } from "giget"; import { downloadTemplate } from 'giget'
import { spawnSync } from "node:child_process"; import { spawnSync } from 'node:child_process'
import path from "node:path"; import path from 'node:path'
const [, , directoryArg] = process.argv; const [, , directoryArg] = process.argv
/** /**
* @typedef {{ * @typedef {{
@ -23,25 +23,25 @@ const [, , directoryArg] = process.argv;
* Create McFly App * Create McFly App
*/ */
async function create() { async function create() {
const defaultDirectory = "mcfly-app"; const defaultDirectory = 'mcfly-app'
consola.box(`Hello! Welcome to ${colorize("bold", "McFly")}!`); consola.box(`Hello! Welcome to ${colorize('bold', 'McFly')}!`)
let directory = directoryArg; let directory = directoryArg
if (!directory) { if (!directory) {
directory = directory =
(await consola.prompt("Give your new project a name:", { (await consola.prompt('Give your new project a name:', {
placeholder: defaultDirectory, placeholder: defaultDirectory,
})) ?? defaultDirectory; })) ?? defaultDirectory
} else { } else {
consola.success(`Using ${directory} as name.`); consola.success(`Using ${directory} as name.`)
} }
if (typeof directory !== "string") { if (typeof directory !== 'string') {
return; return
} }
directory = getSafeDirectory(directory); directory = getSafeDirectory(directory)
const hasErrors = await downloadTemplateToDirectory(directory); const hasErrors = await downloadTemplateToDirectory(directory)
if (!hasErrors) { if (!hasErrors) {
/** /**
@ -50,33 +50,33 @@ async function create() {
const prompts = [ const prompts = [
{ {
prompt: `Would you like to install the dependencies to ${colorize( prompt: `Would you like to install the dependencies to ${colorize(
"bold", 'bold',
directory directory
)}?`, )}?`,
info: "This might take some time depending on your connectivity.", info: 'This might take some time depending on your connectivity.',
startMessage: "Installing dependencies using npm...", startMessage: 'Installing dependencies using npm...',
command: `npm`, command: `npm`,
subCommand: "install", subCommand: 'install',
error: `Install dependencies later with ${colorize( error: `Install dependencies later with ${colorize(
"yellow", 'yellow',
"npm install" 'npm install'
)}`, )}`,
}, },
{ {
prompt: "Would you like to initialize your git repository?", prompt: 'Would you like to initialize your git repository?',
startMessage: "Initializing git repository...", startMessage: 'Initializing git repository...',
command: `git`, command: `git`,
subCommand: "init", subCommand: 'init',
error: `Initialize git repository later with ${colorize( error: `Initialize git repository later with ${colorize(
"yellow", 'yellow',
"git init" 'git init'
)}`, )}`,
}, },
]; ]
const intentions = await askPrompts(prompts, directory); const intentions = await askPrompts(prompts, directory)
if (!!intentions && intentions.length > 0) if (!!intentions && intentions.length > 0)
showResults(directory, intentions[0]); showResults(directory, intentions[0])
} }
} }
@ -86,10 +86,10 @@ async function create() {
* @returns string | undefined * @returns string | undefined
*/ */
function getSafeDirectory(directory) { function getSafeDirectory(directory) {
const { platform } = process; const { platform } = process
const locale = path[platform === `win32` ? `win32` : `posix`]; const locale = path[platform === `win32` ? `win32` : `posix`]
const localePath = directory.split(path.sep).join(locale.sep); const localePath = directory.split(path.sep).join(locale.sep)
return path.normalize(localePath); return path.normalize(localePath)
} }
/** /**
@ -98,22 +98,22 @@ function getSafeDirectory(directory) {
* @returns Promise<boolean> hasErrors * @returns Promise<boolean> hasErrors
*/ */
async function downloadTemplateToDirectory(directory) { async function downloadTemplateToDirectory(directory) {
let hasErrors = false; let hasErrors = false
try { try {
consola.start( consola.start(
`Copying template to ${colorize("bold", getSafeDirectory(directory))}...` `Copying template to ${colorize('bold', getSafeDirectory(directory))}...`
); )
await downloadTemplate("github:ayoayco/mcfly/templates/basic", { await downloadTemplate('github:ayoayco/mcfly/templates/basic', {
dir: directory, dir: directory,
}); })
} catch (_ㆆ) { } catch (_ㆆ) {
consola.error(_ㆆ.message); consola.error(_ㆆ.message)
consola.info("Try a different name.\n"); consola.info('Try a different name.\n')
hasErrors = true; hasErrors = true
} }
return hasErrors; return hasErrors
} }
/** /**
@ -123,37 +123,37 @@ async function downloadTemplateToDirectory(directory) {
* @returns Array<boolean> | undefined * @returns Array<boolean> | undefined
*/ */
async function askPrompts(prompts, cwd) { async function askPrompts(prompts, cwd) {
const results = []; const results = []
for (const p of prompts) { for (const p of prompts) {
const userIntends = await consola.prompt(p.prompt, { const userIntends = await consola.prompt(p.prompt, {
type: "confirm", type: 'confirm',
}); })
if (typeof userIntends !== "boolean") { if (typeof userIntends !== 'boolean') {
return; return
} }
if (userIntends) { if (userIntends) {
p.info && consola.info(p.info); p.info && consola.info(p.info)
consola.start(p.startMessage); consola.start(p.startMessage)
try { try {
spawnSync(p.command, [p.subCommand], { spawnSync(p.command, [p.subCommand], {
cwd, cwd,
shell: true, shell: true,
timeout: 100_000, timeout: 100_000,
stdio: "inherit", stdio: 'inherit',
}); })
consola.success("Done!"); consola.success('Done!')
} catch (_ㆆ) { } catch (_ㆆ) {
consola.error(_ㆆ.message); consola.error(_ㆆ.message)
consola.info(p.error + "\n"); consola.info(p.error + '\n')
} }
} }
results.push(userIntends); results.push(userIntends)
} }
return results; return results
} }
/** /**
@ -163,28 +163,28 @@ async function askPrompts(prompts, cwd) {
*/ */
function showResults(directory, installDeps) { function showResults(directory, installDeps) {
let nextActions = [ let nextActions = [
`Go to your project by running ${colorize("yellow", `cd ${directory}`)}`, `Go to your project by running ${colorize('yellow', `cd ${directory}`)}`,
]; ]
if (!installDeps) { if (!installDeps) {
nextActions.push( nextActions.push(
`Install the dependencies with ${colorize("yellow", "npm install")}` `Install the dependencies with ${colorize('yellow', 'npm install')}`
); )
} }
nextActions = nextActions.concat([ nextActions = nextActions.concat([
`Start the dev server with ${colorize("yellow", "npm start")}`, `Start the dev server with ${colorize('yellow', 'npm start')}`,
`Join us at ${colorize("blue", "https://ayco.io/gh/McFly")}`, `Join us at ${colorize('blue', 'https://ayco.io/gh/McFly')}`,
]); ])
const result = `🎉 Your new ${colorize( const result = `🎉 Your new ${colorize(
"bold", 'bold',
"McFly" 'McFly'
)} app is ready: ${directory}\n\nNext actions: ${nextActions )} app is ready: ${directory}\n\nNext actions: ${nextActions
.map((action, index) => `\n${++index}. ${action}`) .map((action, index) => `\n${++index}. ${action}`)
.join("")}`; .join('')}`
consola.box(result); consola.box(result)
} }
create(); create()

View file

@ -5,10 +5,10 @@
* @type {import("prettier").Config} * @type {import("prettier").Config}
*/ */
const config = { const config = {
trailingComma: "es5", trailingComma: 'es5',
tabWidth: 2, tabWidth: 2,
semi: false, semi: false,
singleQuote: true, singleQuote: true,
}; }
export default config; export default config

View file

@ -1,4 +1,4 @@
import { defineMcFlyConfig } from "#imports"; import { defineMcFlyConfig } from '#imports'
export default defineMcFlyConfig({ export default defineMcFlyConfig({
components: "js", components: 'js',
}); })

View file

@ -1,14 +1,14 @@
export default defineNitroConfig({ export default defineNitroConfig({
extends: "@mcflyjs/config", extends: '@mcflyjs/config',
devServer: { devServer: {
watch: ["../packages"], watch: ['../packages'],
}, },
routeRules: { routeRules: {
"/chat": { '/chat': {
redirect: { redirect: {
to: "https://matrix.to/#/#mcfly:matrix.org", to: 'https://matrix.to/#/#mcfly:matrix.org',
statusCode: 302, statusCode: 302,
}, },
}, },
@ -19,5 +19,5 @@ export default defineNitroConfig({
brotli: true, brotli: true,
}, },
compatibilityDate: "2024-12-08", compatibilityDate: '2024-12-08',
}); })

File diff suppressed because one or more lines are too long

View file

@ -5,5 +5,5 @@
* ...reusable code are in ./src/components * ...reusable code are in ./src/components
* @see https://ayco.io/gh/McFly#special-directories * @see https://ayco.io/gh/McFly#special-directories
*/ */
import config from "../mcfly.config.mjs"; import config from '../mcfly.config.mjs'
export default useMcFlyRoute({ config, storage: useStorage() }); export default useMcFlyRoute({ config, storage: useStorage() })

View file

@ -4,9 +4,9 @@
*/ */
class ClickableText extends WebComponent { class ClickableText extends WebComponent {
onInit() { onInit() {
this.onclick = () => alert("Thank you for clicking the text!"); this.onclick = () => alert('Thank you for clicking the text!')
} }
get template() { get template() {
return `<span style="cursor:pointer">Click me too!</span>`; return `<span style="cursor:pointer">Click me too!</span>`
} }
} }

View file

@ -1,41 +1,41 @@
class CodeBlockComponent extends HTMLElement { class CodeBlockComponent extends HTMLElement {
connectedCallback() { connectedCallback() {
const trimmed = this.innerHTML.trim(); const trimmed = this.innerHTML.trim()
const lang = this.getAttribute("language"); const lang = this.getAttribute('language')
const inline = this.getAttribute("inline") !== null; const inline = this.getAttribute('inline') !== null
this.innerHTML = ` this.innerHTML = `
<pre id="pre"><code id="code">${trimmed}</code></pre> <pre id="pre"><code id="code">${trimmed}</code></pre>
`; `
/** /**
* @type {HTMLPreElement} * @type {HTMLPreElement}
*/ */
const pre = this.querySelector("#pre"); const pre = this.querySelector('#pre')
if (lang) { if (lang) {
pre.className = `language-${lang}`; pre.className = `language-${lang}`
} }
/** /**
* @type {Partial<CSSStyleDeclaration>} * @type {Partial<CSSStyleDeclaration>}
*/ */
const style = { const style = {
background: "#f5f2f0", background: '#f5f2f0',
padding: "1em", padding: '1em',
margin: "1em 0", margin: '1em 0',
fontSize: "large", fontSize: 'large',
overflow: "auto", overflow: 'auto',
borderRadius: '5px' borderRadius: '5px',
}; }
if (inline) { if (inline) {
style.display = 'inline'; style.display = 'inline'
style.padding = '0.3em'; style.padding = '0.3em'
} }
Object.keys(style).forEach((rule) => { Object.keys(style).forEach((rule) => {
pre.style[rule] = style[rule]; pre.style[rule] = style[rule]
}); })
} }
} }

View file

@ -8,14 +8,16 @@ class HelloWorld extends WebComponent {
count: 0, count: 0,
} }
updateLabel(){ updateLabel() {
this.props.myName = `Clicked ${++this.props.count}x`; this.props.myName = `Clicked ${++this.props.count}x`
} }
get template() { get template() {
return html` return html` <button
<button onClick=${() => this.updateLabel()} style="cursor:pointer"> onClick=${() => this.updateLabel()}
style="cursor:pointer"
>
Hello ${this.props.myName}! Hello ${this.props.myName}!
</button>`; </button>`
} }
} }

View file

@ -1,22 +1,22 @@
class HelloWorld extends HTMLElement { class HelloWorld extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ["my-name"]; return ['my-name']
} }
connectedCallback() { connectedCallback() {
let count = 0; let count = 0
const currentName = this.getAttribute('my-name'); const currentName = this.getAttribute('my-name')
if (!currentName) { if (!currentName) {
this.setAttribute('my-name', 'World') this.setAttribute('my-name', 'World')
} }
this.onclick = () => this.setAttribute("my-name", `Clicked ${++count}x`); this.onclick = () => this.setAttribute('my-name', `Clicked ${++count}x`)
} }
attributeChangedCallback(property, previousValue, currentValue) { attributeChangedCallback(property, previousValue, currentValue) {
if (property === "my-name" && previousValue !== currentValue) { if (property === 'my-name' && previousValue !== currentValue) {
this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`; this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`
} }
} }
} }

View file

@ -1,4 +1,4 @@
import { defineMcFlyConfig } from "#imports"; import { defineMcFlyConfig } from '#imports'
export default defineMcFlyConfig({ export default defineMcFlyConfig({
components: "js", components: 'js',
}); })

View file

@ -1 +1 @@
export default defineNitroConfig({ extends: '@mcflyjs/config' }); export default defineNitroConfig({ extends: '@mcflyjs/config' })

View file

@ -5,5 +5,5 @@
* ...reusable code are in ./src/components * ...reusable code are in ./src/components
* @see https://ayco.io/gh/McFly#special-directories * @see https://ayco.io/gh/McFly#special-directories
*/ */
import config from "../mcfly.config.mjs"; import config from '../mcfly.config.mjs'
export default useMcFlyRoute({ config, storage: useStorage() }); export default useMcFlyRoute({ config, storage: useStorage() })

View file

@ -5,7 +5,7 @@
*/ */
export default eventHandler(() => { export default eventHandler(() => {
return { return {
user: "AYO", user: 'AYO',
date: new Date(), date: new Date(),
}; }
}); })

View file

@ -1,41 +1,41 @@
class CodeBlockComponent extends HTMLElement { class CodeBlockComponent extends HTMLElement {
connectedCallback() { connectedCallback() {
const trimmed = this.innerHTML.trim(); const trimmed = this.innerHTML.trim()
const lang = this.getAttribute("language"); const lang = this.getAttribute('language')
const inline = this.getAttribute("inline") !== null; const inline = this.getAttribute('inline') !== null
this.innerHTML = ` this.innerHTML = `
<pre id="pre"><code id="code">${trimmed}</code></pre> <pre id="pre"><code id="code">${trimmed}</code></pre>
`; `
/** /**
* @type {HTMLPreElement} * @type {HTMLPreElement}
*/ */
const pre = this.querySelector("#pre"); const pre = this.querySelector('#pre')
if (lang) { if (lang) {
pre.className = `language-${lang}`; pre.className = `language-${lang}`
} }
/** /**
* @type {Partial<CSSStyleDeclaration>} * @type {Partial<CSSStyleDeclaration>}
*/ */
const style = { const style = {
background: "#f5f2f0", background: '#f5f2f0',
padding: "1em", padding: '1em',
margin: "1em 0", margin: '1em 0',
fontSize: "large", fontSize: 'large',
overflow: "auto", overflow: 'auto',
borderRadius: '5px' borderRadius: '5px',
}; }
if (inline) { if (inline) {
style.display = 'inline'; style.display = 'inline'
style.padding = '0.3em'; style.padding = '0.3em'
} }
Object.keys(style).forEach((rule) => { Object.keys(style).forEach((rule) => {
pre.style[rule] = style[rule]; pre.style[rule] = style[rule]
}); })
} }
} }

View file

@ -5,7 +5,7 @@
class MyHelloWorld extends WebComponent { class MyHelloWorld extends WebComponent {
static props = { static props = {
myName: 'World', myName: 'World',
count: 0 count: 0,
} }
updateLabel() { updateLabel() {
@ -13,9 +13,11 @@ class MyHelloWorld extends WebComponent {
} }
get template() { get template() {
return html` return html` <button
<button onClick=${() => this.updateLabel()} style="cursor:pointer"> onClick=${() => this.updateLabel()}
style="cursor:pointer"
>
Hello ${this.props.myName}! Hello ${this.props.myName}!
</button>`; </button>`
} }
} }