chore: format code
This commit is contained in:
parent
a362adab90
commit
c25c5e03d4
35 changed files with 1365 additions and 615 deletions
28
.vscode/settings.json
vendored
28
.vscode/settings.json
vendored
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
// "editor.formatOnSave": false,
|
||||
"js/ts.implicitProjectConfig.checkJs": true,
|
||||
"cSpell.words": [
|
||||
"citty",
|
||||
"consola",
|
||||
"estree",
|
||||
"giget",
|
||||
"mcfly",
|
||||
"mcflyjs",
|
||||
"nitropack",
|
||||
"ultrahtml",
|
||||
"unstorage"
|
||||
]
|
||||
}
|
||||
// "editor.formatOnSave": false,
|
||||
"js/ts.implicitProjectConfig.checkJs": true,
|
||||
"cSpell.words": [
|
||||
"citty",
|
||||
"consola",
|
||||
"estree",
|
||||
"giget",
|
||||
"mcfly",
|
||||
"mcflyjs",
|
||||
"nitropack",
|
||||
"ultrahtml",
|
||||
"unstorage"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import globals from 'globals'
|
||||
import pluginJs from '@eslint/js'
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
|
@ -7,12 +7,12 @@ export default [
|
|||
pluginJs.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
"dist/*",
|
||||
".output/*",
|
||||
".nitro/*",
|
||||
"node-modules*",
|
||||
"site/*",
|
||||
"templates/*",
|
||||
'dist/*',
|
||||
'.output/*',
|
||||
'.nitro/*',
|
||||
'node-modules*',
|
||||
'site/*',
|
||||
'templates/*',
|
||||
],
|
||||
},
|
||||
];
|
||||
]
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { consola } from "consola";
|
||||
import { defineCommand } from "citty";
|
||||
import { execSync } from "node:child_process";
|
||||
import { consola } from 'consola'
|
||||
import { defineCommand } from 'citty'
|
||||
import { execSync } from 'node:child_process'
|
||||
|
||||
function build() {
|
||||
consola.start("Building project...");
|
||||
try {
|
||||
execSync(`npx nitropack build`, { stdio: "inherit" });
|
||||
} catch (err) {
|
||||
consola.error(err);
|
||||
}
|
||||
consola.start('Building project...')
|
||||
try {
|
||||
execSync(`npx nitropack build`, { stdio: 'inherit' })
|
||||
} catch (err) {
|
||||
consola.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "prepare",
|
||||
description: "Builds the McFly project for production.",
|
||||
name: 'prepare',
|
||||
description: 'Builds the McFly project for production.',
|
||||
},
|
||||
|
||||
async run() {
|
||||
build()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
export const exportedForTest = {
|
||||
build,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { consola } from "consola";
|
||||
import { defineCommand } from "citty";
|
||||
import { consola } from 'consola'
|
||||
import { defineCommand } from 'citty'
|
||||
|
||||
function generate() {
|
||||
consola.box("Generate a McFly building block (In-progress)");
|
||||
consola.box('Generate a McFly building block (In-progress)')
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "prepare",
|
||||
description: "Generates building blocks for a McFly app.",
|
||||
name: 'prepare',
|
||||
description: 'Generates building blocks for a McFly app.',
|
||||
},
|
||||
run() {
|
||||
generate();
|
||||
generate()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
export const exportedForTest = {
|
||||
generate,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { consola } from "consola";
|
||||
import { defineCommand } from "citty";
|
||||
import { execSync } from 'node:child_process'
|
||||
import { consola } from 'consola'
|
||||
import { defineCommand } from 'citty'
|
||||
|
||||
function createNew(args) {
|
||||
const directory = args.dir || args._dir;
|
||||
const command = directory
|
||||
? `npm create mcfly@latest ${directory}`
|
||||
: "npm create mcfly@latest";
|
||||
try {
|
||||
execSync(command, { stdio: "inherit" });
|
||||
} catch (e) {
|
||||
consola.error(e);
|
||||
}
|
||||
const directory = args.dir || args._dir
|
||||
const command = directory
|
||||
? `npm create mcfly@latest ${directory}`
|
||||
: 'npm create mcfly@latest'
|
||||
try {
|
||||
execSync(command, { stdio: 'inherit' })
|
||||
} catch (e) {
|
||||
consola.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "prepare",
|
||||
description: "Creates a new McFly project.",
|
||||
name: 'prepare',
|
||||
description: 'Creates a new McFly project.',
|
||||
},
|
||||
args: {
|
||||
dir: {
|
||||
type: "string",
|
||||
description: "project root directory",
|
||||
type: 'string',
|
||||
description: 'project root directory',
|
||||
required: false,
|
||||
},
|
||||
_dir: {
|
||||
type: "positional",
|
||||
description: "project root directory (prefer using `--dir`)",
|
||||
type: 'positional',
|
||||
description: 'project root directory (prefer using `--dir`)',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
async run({ args }) {
|
||||
createNew(args)
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
export const exportedForTest = {
|
||||
createNew
|
||||
}
|
||||
createNew,
|
||||
}
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { consola } from "consola";
|
||||
import { defineCommand } from "citty";
|
||||
import { execSync } from "node:child_process";
|
||||
import { consola } from 'consola'
|
||||
import { defineCommand } from 'citty'
|
||||
import { execSync } from 'node:child_process'
|
||||
|
||||
function prepare() {
|
||||
consola.start("Preparing McFly workspace...");
|
||||
consola.start('Preparing McFly workspace...')
|
||||
|
||||
let err;
|
||||
let err
|
||||
|
||||
try {
|
||||
execSync("npx nitropack prepare", { stdio: "inherit" });
|
||||
} catch (e) {
|
||||
consola.error(e);
|
||||
err = e;
|
||||
}
|
||||
try {
|
||||
execSync('npx nitropack prepare', { stdio: 'inherit' })
|
||||
} catch (e) {
|
||||
consola.error(e)
|
||||
err = e
|
||||
}
|
||||
|
||||
if (err) {
|
||||
consola.fail(
|
||||
"McFly workspace preparation failed. Please make sure dependencies are installed.\n"
|
||||
);
|
||||
} else consola.success("Done\n");
|
||||
if (err) {
|
||||
consola.fail(
|
||||
'McFly workspace preparation failed. Please make sure dependencies are installed.\n'
|
||||
)
|
||||
} else consola.success('Done\n')
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "prepare",
|
||||
description: "Prepares the McFly workspace.",
|
||||
name: 'prepare',
|
||||
description: 'Prepares the McFly workspace.',
|
||||
},
|
||||
run() {
|
||||
prepare();
|
||||
prepare()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
export const exportedForTest = {
|
||||
prepare
|
||||
}
|
||||
prepare,
|
||||
}
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { consola } from "consola";
|
||||
import { colorize } from "consola/utils";
|
||||
import { defineCommand } from "citty";
|
||||
import { execSync } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { consola } from 'consola'
|
||||
import { colorize } from 'consola/utils'
|
||||
import { defineCommand } from 'citty'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
async function printInfo() {
|
||||
try {
|
||||
const _require = createRequire(import.meta.url);
|
||||
const mcflyPkg = await _require("@mcflyjs/core/package.json");
|
||||
const mcflyPkgVersion = `McFly ${colorize("bold", mcflyPkg.version)}`;
|
||||
const nitroPkg = await _require("nitropack/package.json");
|
||||
const nitroPkgVersion = `Nitro ${nitroPkg.version}`;
|
||||
const _require = createRequire(import.meta.url)
|
||||
const mcflyPkg = await _require('@mcflyjs/core/package.json')
|
||||
const mcflyPkgVersion = `McFly ${colorize('bold', mcflyPkg.version)}`
|
||||
const nitroPkg = await _require('nitropack/package.json')
|
||||
const nitroPkgVersion = `Nitro ${nitroPkg.version}`
|
||||
consola.log(
|
||||
`${colorize("blue", mcflyPkgVersion)} ${colorize("dim", nitroPkgVersion)}`
|
||||
);
|
||||
`${colorize('blue', mcflyPkgVersion)} ${colorize('dim', nitroPkgVersion)}`
|
||||
)
|
||||
} catch (e) {
|
||||
consola.error(e);
|
||||
consola.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
function serve() {
|
||||
try {
|
||||
execSync(`npx nitropack dev`, { stdio: "inherit" });
|
||||
execSync(`npx nitropack dev`, { stdio: 'inherit' })
|
||||
} catch (e) {
|
||||
consola.error(e);
|
||||
consola.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default defineCommand({
|
||||
meta: {
|
||||
name: "prepare",
|
||||
description: "Runs the dev server.",
|
||||
name: 'prepare',
|
||||
description: 'Runs the dev server.',
|
||||
},
|
||||
async run() {
|
||||
await printInfo();
|
||||
serve();
|
||||
await printInfo()
|
||||
serve()
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
export const exportedForTest = {
|
||||
serve,
|
||||
printInfo,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
#!/usr/bin/env node
|
||||
import { defineCommand, runMain } from "citty";
|
||||
import { defineCommand, runMain } from 'citty'
|
||||
|
||||
const main = defineCommand({
|
||||
meta: {
|
||||
name: "mcfly",
|
||||
description: "McFly CLI",
|
||||
name: 'mcfly',
|
||||
description: 'McFly CLI',
|
||||
},
|
||||
subCommands: {
|
||||
new: () => import("./commands/new.mjs").then((r) => r.default),
|
||||
serve: () => import("./commands/serve.mjs").then((r) => r.default),
|
||||
build: () => import("./commands/build.mjs").then((r) => r.default),
|
||||
prepare: () => import("./commands/prepare.mjs").then((r) => r.default),
|
||||
generate: () => import("./commands/generate.mjs").then((r) => r.default),
|
||||
g: () => import("./commands/generate.mjs").then((r) => r.default),
|
||||
new: () => import('./commands/new.mjs').then((r) => r.default),
|
||||
serve: () => import('./commands/serve.mjs').then((r) => r.default),
|
||||
build: () => import('./commands/build.mjs').then((r) => r.default),
|
||||
prepare: () => import('./commands/prepare.mjs').then((r) => r.default),
|
||||
generate: () => import('./commands/generate.mjs').then((r) => r.default),
|
||||
g: () => import('./commands/generate.mjs').then((r) => r.default),
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
runMain(main);
|
||||
runMain(main)
|
||||
|
||||
export const exportedForTest = {
|
||||
main,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
import consola from "consola";
|
||||
import { vi, expect, test } from "vitest";
|
||||
import { exportedForTest } from "../commands/build.mjs";
|
||||
import consola from 'consola'
|
||||
import { vi, expect, test } from 'vitest'
|
||||
import { exportedForTest } from '../commands/build.mjs'
|
||||
|
||||
const testFn = exportedForTest.build;
|
||||
const testFn = exportedForTest.build
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
vi.mock('node:child_process', () => {
|
||||
return {
|
||||
execSync: mocks.execSync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
test("start build with message", () => {
|
||||
const message = "Building project...";
|
||||
const spy = vi.spyOn(consola, "start");
|
||||
test('start build with message', () => {
|
||||
const message = 'Building project...'
|
||||
const spy = vi.spyOn(consola, 'start')
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(message);
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith(message)
|
||||
})
|
||||
|
||||
test("execute nitropack build", () => {
|
||||
const command = "npx nitropack build";
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute nitropack build', () => {
|
||||
const command = 'npx nitropack build'
|
||||
const param = { stdio: 'inherit' }
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(mocks.execSync).toHaveBeenCalledWith(command, param);
|
||||
});
|
||||
expect(mocks.execSync).toHaveBeenCalledWith(command, param)
|
||||
})
|
||||
|
||||
test("catch error", () => {
|
||||
const spy = vi.spyOn(consola, "error");
|
||||
test('catch error', () => {
|
||||
const spy = vi.spyOn(consola, 'error')
|
||||
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'))
|
||||
})
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { expect, test, vi } from "vitest";
|
||||
import { exportedForTest } from "../commands/generate.mjs";
|
||||
import consola from "consola";
|
||||
import { expect, test, vi } from 'vitest'
|
||||
import { exportedForTest } from '../commands/generate.mjs'
|
||||
import consola from 'consola'
|
||||
|
||||
const testFn = exportedForTest.generate;
|
||||
const testFn = exportedForTest.generate
|
||||
|
||||
test("show box message in-progress", () => {
|
||||
const spy = vi.spyOn(consola, "box");
|
||||
test('show box message in-progress', () => {
|
||||
const spy = vi.spyOn(consola, 'box')
|
||||
|
||||
testFn();
|
||||
const arg = spy.mock.calls[0][0];
|
||||
testFn()
|
||||
const arg = spy.mock.calls[0][0]
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(arg).toContain("In-progress");
|
||||
});
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(arg).toContain('In-progress')
|
||||
})
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { test } from "vitest";
|
||||
import { exportedForTest } from "..";
|
||||
import { expect } from "vitest";
|
||||
import { test } from 'vitest'
|
||||
import { exportedForTest } from '..'
|
||||
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) => {
|
||||
expect(testObj.subCommands[key]).toBeTypeOf("function");
|
||||
expect(testObj.subCommands[key].name).toBe(key);
|
||||
});
|
||||
});
|
||||
expect(testObj.subCommands[key]).toBeTypeOf('function')
|
||||
expect(testObj.subCommands[key].name).toBe(key)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
import { expect, test, vi } from "vitest";
|
||||
import { exportedForTest } from "../commands/new.mjs";
|
||||
import { execSync } from "node:child_process";
|
||||
import consola from "consola";
|
||||
import { expect, test, vi } from 'vitest'
|
||||
import { exportedForTest } from '../commands/new.mjs'
|
||||
import { execSync } from 'node:child_process'
|
||||
import consola from 'consola'
|
||||
|
||||
const testFn = exportedForTest.createNew;
|
||||
const baseCommand = `npm create mcfly@latest`;
|
||||
const testFn = exportedForTest.createNew
|
||||
const baseCommand = `npm create mcfly@latest`
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
vi.mock('node:child_process', () => {
|
||||
return {
|
||||
execSync: mocks.execSync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
test("execute create mcfly", () => {
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute create mcfly', () => {
|
||||
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", () => {
|
||||
const dir = "fake-dir";
|
||||
const command = `${baseCommand} ${dir}`;
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute create mcfly with no dir', () => {
|
||||
const dir = 'fake-dir'
|
||||
const command = `${baseCommand} ${dir}`
|
||||
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", () => {
|
||||
const dir = "fake-dir";
|
||||
const command = `${baseCommand} ${dir}`;
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute create mcfly with dir', () => {
|
||||
const dir = 'fake-dir'
|
||||
const command = `${baseCommand} ${dir}`
|
||||
const param = { stdio: 'inherit' }
|
||||
|
||||
testFn({ dir });
|
||||
testFn({ dir })
|
||||
|
||||
expect(execSync).toHaveBeenCalledWith(command, param);
|
||||
});
|
||||
expect(execSync).toHaveBeenCalledWith(command, param)
|
||||
})
|
||||
|
||||
test("execute create mcfly with _dir", () => {
|
||||
const dir = "fake-dir";
|
||||
const command = `${baseCommand} ${dir}`;
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute create mcfly with _dir', () => {
|
||||
const dir = 'fake-dir'
|
||||
const command = `${baseCommand} ${dir}`
|
||||
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", () => {
|
||||
const dir = "preferred-dir";
|
||||
const command = `${baseCommand} ${dir}`;
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute create mcfly with dir preferred over _dir', () => {
|
||||
const dir = 'preferred-dir'
|
||||
const command = `${baseCommand} ${dir}`
|
||||
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", () => {
|
||||
const dir = "fake-dir";
|
||||
const spy = vi.spyOn(consola, "error");
|
||||
test('catch error', () => {
|
||||
const dir = 'fake-dir'
|
||||
const spy = vi.spyOn(consola, 'error')
|
||||
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'))
|
||||
})
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
import { test, expect, vi } from "vitest";
|
||||
import { exportedForTest } from "../commands/prepare.mjs";
|
||||
import consola from "consola";
|
||||
import { execSync } from "node:child_process";
|
||||
import { test, expect, vi } from 'vitest'
|
||||
import { exportedForTest } from '../commands/prepare.mjs'
|
||||
import consola from 'consola'
|
||||
import { execSync } from 'node:child_process'
|
||||
|
||||
const testFn = exportedForTest.prepare;
|
||||
const testFn = exportedForTest.prepare
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
vi.mock('node:child_process', () => {
|
||||
return {
|
||||
execSync: mocks.execSync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
test("start prepare script", () => {
|
||||
const spy = vi.spyOn(consola, "start");
|
||||
test('start prepare script', () => {
|
||||
const spy = vi.spyOn(consola, 'start')
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("execute nitropack prepare", () => {
|
||||
const successSpy = vi.spyOn(consola, "success");
|
||||
const command = "npx nitropack prepare";
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute nitropack prepare', () => {
|
||||
const successSpy = vi.spyOn(consola, 'success')
|
||||
const command = 'npx nitropack prepare'
|
||||
const param = { stdio: 'inherit' }
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(execSync).toHaveBeenCalledWith(command, param);
|
||||
expect(successSpy).toHaveBeenCalled();
|
||||
});
|
||||
expect(execSync).toHaveBeenCalledWith(command, param)
|
||||
expect(successSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("catch error", () => {
|
||||
const errSpy = vi.spyOn(consola, "error");
|
||||
const failSpy = vi.spyOn(consola, "fail");
|
||||
test('catch error', () => {
|
||||
const errSpy = vi.spyOn(consola, 'error')
|
||||
const failSpy = vi.spyOn(consola, 'fail')
|
||||
mocks.execSync.mockImplementationOnce(() => {
|
||||
throw new Error();
|
||||
});
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(errSpy).toHaveBeenCalled();
|
||||
expect(failSpy).toHaveBeenCalled();
|
||||
});
|
||||
expect(errSpy).toHaveBeenCalled()
|
||||
expect(failSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
import { describe, expect, test, vi } from "vitest";
|
||||
import { exportedForTest } from "../commands/serve.mjs";
|
||||
import consola from "consola";
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { exportedForTest } from '../commands/serve.mjs'
|
||||
import consola from 'consola'
|
||||
|
||||
describe("FUNCTION: serve()", () => {
|
||||
const testFn = exportedForTest.serve;
|
||||
describe('FUNCTION: serve()', () => {
|
||||
const testFn = exportedForTest.serve
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
vi.mock('node:child_process', () => {
|
||||
return {
|
||||
execSync: mocks.execSync,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
test("execute nitropack serve", async () => {
|
||||
const command = `npx nitropack dev`;
|
||||
const param = { stdio: "inherit" };
|
||||
test('execute nitropack serve', async () => {
|
||||
const command = `npx nitropack dev`
|
||||
const param = { stdio: 'inherit' }
|
||||
|
||||
testFn();
|
||||
testFn()
|
||||
|
||||
expect(mocks.execSync).toHaveBeenCalledWith(command, param);
|
||||
});
|
||||
expect(mocks.execSync).toHaveBeenCalledWith(command, param)
|
||||
})
|
||||
|
||||
test("catch error", () => {
|
||||
const spy = vi.spyOn(consola, "error");
|
||||
test('catch error', () => {
|
||||
const spy = vi.spyOn(consola, 'error')
|
||||
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()", () => {
|
||||
const testFn = exportedForTest.printInfo;
|
||||
describe('FUNCTION: printInfo()', () => {
|
||||
const testFn = exportedForTest.printInfo
|
||||
|
||||
const createRequireMocks = vi.hoisted(() => {
|
||||
return {
|
||||
createRequire: vi.fn(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("node:module", () => {
|
||||
vi.mock('node:module', () => {
|
||||
return {
|
||||
createRequire: createRequireMocks.createRequire,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
test("log mcfly and nitro versions", async () => {
|
||||
const spy = vi.spyOn(consola, "log");
|
||||
const fakeMessage = "McFly -1.0.0 Nitro -1.0.0";
|
||||
test('log mcfly and nitro versions', async () => {
|
||||
const spy = vi.spyOn(consola, 'log')
|
||||
const fakeMessage = 'McFly -1.0.0 Nitro -1.0.0'
|
||||
createRequireMocks.createRequire.mockImplementationOnce(() => {
|
||||
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("Nitro");
|
||||
expect(spy).toHaveBeenCalledWith(fakeMessage);
|
||||
});
|
||||
expect(spy.mock.calls[0][0]).toContain('McFly')
|
||||
expect(spy.mock.calls[0][0]).toContain('Nitro')
|
||||
expect(spy).toHaveBeenCalledWith(fakeMessage)
|
||||
})
|
||||
|
||||
test("catch error", async () => {
|
||||
test('catch error', async () => {
|
||||
createRequireMocks.createRequire.mockImplementationOnce(() => {
|
||||
throw new Error("error");
|
||||
});
|
||||
const spy = vi.spyOn(consola, "error");
|
||||
throw new Error('error')
|
||||
})
|
||||
const spy = vi.spyOn(consola, 'error')
|
||||
|
||||
await testFn();
|
||||
await testFn()
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(new Error("error"));
|
||||
});
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith(new Error('error'))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { coverageConfigDefaults, defineConfig } from "vitest/config";
|
||||
import { coverageConfigDefaults, defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
enabled: true,
|
||||
provider: "v8",
|
||||
reporter: ["html", "text"],
|
||||
exclude: ["html/**", ...coverageConfigDefaults.exclude],
|
||||
provider: 'v8',
|
||||
reporter: ['html', 'text'],
|
||||
exclude: ['html/**', ...coverageConfigDefaults.exclude],
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
|
@ -9,33 +9,33 @@
|
|||
export default function () {
|
||||
return {
|
||||
framework: {
|
||||
name: "McFly",
|
||||
name: 'McFly',
|
||||
},
|
||||
compatibilityDate: "2024-12-08",
|
||||
compatibilityDate: '2024-12-08',
|
||||
devServer: {
|
||||
watch: ["./src/pages", "./src/components"],
|
||||
watch: ['./src/pages', './src/components'],
|
||||
},
|
||||
serverAssets: [
|
||||
{
|
||||
baseName: "pages",
|
||||
dir: "./src/pages",
|
||||
baseName: 'pages',
|
||||
dir: './src/pages',
|
||||
},
|
||||
{
|
||||
baseName: "components",
|
||||
dir: "./src/components",
|
||||
baseName: 'components',
|
||||
dir: './src/components',
|
||||
},
|
||||
],
|
||||
imports: {
|
||||
presets: [
|
||||
{
|
||||
from: "web-component-base",
|
||||
imports: ["WebComponent", "html", "attachEffect"],
|
||||
from: 'web-component-base',
|
||||
imports: ['WebComponent', 'html', 'attachEffect'],
|
||||
},
|
||||
{
|
||||
from: "@mcflyjs/core",
|
||||
imports: ["useMcFlyRoute", "defineMcFlyConfig"],
|
||||
from: '@mcflyjs/core',
|
||||
imports: ['useMcFlyRoute', 'defineMcFlyConfig'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
* @returns {function(): McFlyConfig}
|
||||
*/
|
||||
export function defineMcFlyConfig(config) {
|
||||
return () => config;
|
||||
return () => config
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { eventHandler } from "h3";
|
||||
import { ELEMENT_NODE, parse, render, renderSync, walkSync } from "ultrahtml";
|
||||
import { parseScript } from "esprima";
|
||||
import { eventHandler } from 'h3'
|
||||
import { ELEMENT_NODE, parse, render, renderSync, walkSync } from 'ultrahtml'
|
||||
import { parseScript } from 'esprima'
|
||||
|
||||
/**
|
||||
* @typedef {import('./define-config.js').McFlyConfig} Config
|
||||
|
@ -21,36 +21,36 @@ import { parseScript } from "esprima";
|
|||
*/
|
||||
export function useMcFlyRoute({ config, storage }) {
|
||||
return eventHandler(async (event) => {
|
||||
const { path } = event;
|
||||
const { components: componentType } = config();
|
||||
let html = await getHtml(path, storage);
|
||||
const { path } = event
|
||||
const { components: componentType } = config()
|
||||
let html = await getHtml(path, storage)
|
||||
|
||||
if (html) {
|
||||
const transforms = [doSetUp, deleteServerScripts];
|
||||
const transforms = [doSetUp, deleteServerScripts]
|
||||
|
||||
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) {
|
||||
html = await insertRegistry(html.toString(), componentType, storage);
|
||||
html = await insertRegistry(html.toString(), componentType, storage)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
html ??
|
||||
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 }
|
||||
)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function getPurePath(path) {
|
||||
return path.split("?")[0];
|
||||
return path.split('?')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,17 +60,17 @@ function getPurePath(path) {
|
|||
* @returns {Promise<StorageValue>}
|
||||
*/
|
||||
async function getHtml(path, storage) {
|
||||
const purePath = getPurePath(path);
|
||||
const purePath = getPurePath(path)
|
||||
const rawPath =
|
||||
purePath[purePath.length - 1] === "/" ? purePath.slice(0, -1) : purePath;
|
||||
const filename = rawPath === "" ? "/index.html" : `${rawPath}.html`;
|
||||
const fallback = getPath(rawPath + "/index.html");
|
||||
const filePath = getPath(filename);
|
||||
let html = await storage.getItem(filePath);
|
||||
if (!html) html = await storage.getItem(fallback);
|
||||
if (!html) html = await storage.getItem(getPath("/404.html"));
|
||||
purePath[purePath.length - 1] === '/' ? purePath.slice(0, -1) : purePath
|
||||
const filename = rawPath === '' ? '/index.html' : `${rawPath}.html`
|
||||
const fallback = getPath(rawPath + '/index.html')
|
||||
const filePath = getPath(filename)
|
||||
let html = await storage.getItem(filePath)
|
||||
if (!html) html = await storage.getItem(fallback)
|
||||
if (!html) html = await storage.getItem(getPath('/404.html'))
|
||||
|
||||
return html;
|
||||
return html
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +79,7 @@ async function getHtml(path, storage) {
|
|||
* @returns {string}
|
||||
*/
|
||||
function getPath(filename) {
|
||||
return `assets:pages${filename}`;
|
||||
return `assets:pages${filename}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,21 +90,21 @@ function getPath(filename) {
|
|||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function insertRegistry(html, type, storage) {
|
||||
const ast = parse(html);
|
||||
const componentFiles = await getFiles(type, storage);
|
||||
const ast = parse(html)
|
||||
const componentFiles = await getFiles(type, storage)
|
||||
const availableComponents = componentFiles.map((key) =>
|
||||
key.replace(`.${type}`, "")
|
||||
);
|
||||
key.replace(`.${type}`, '')
|
||||
)
|
||||
|
||||
const usedCustomElements = [];
|
||||
const usedCustomElements = []
|
||||
|
||||
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) {
|
||||
usedCustomElements.push(usedElement);
|
||||
usedCustomElements.push(usedElement)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// insert registry script to head
|
||||
if (usedCustomElements.length > 0) {
|
||||
|
@ -112,15 +112,15 @@ async function insertRegistry(html, type, storage) {
|
|||
usedCustomElements,
|
||||
type,
|
||||
storage
|
||||
);
|
||||
)
|
||||
walkSync(ast, (node) => {
|
||||
if (node.type === ELEMENT_NODE && node.name === "head") {
|
||||
node.children.push(parse(registryScript));
|
||||
if (node.type === ELEMENT_NODE && node.name === 'head') {
|
||||
node.children.push(parse(registryScript))
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return render(ast);
|
||||
return render(ast)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,41 +131,41 @@ async function insertRegistry(html, type, storage) {
|
|||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function buildRegistry(usedCustomElements, type, storage) {
|
||||
let registryScript = `<script type='module'>`;
|
||||
let isBaseClassImported = false;
|
||||
let classesImported = [];
|
||||
let registryScript = `<script type='module'>`
|
||||
let isBaseClassImported = false
|
||||
let classesImported = []
|
||||
|
||||
for (const name of usedCustomElements) {
|
||||
const content = await storage.getItem(`assets:components:${name}.${type}`);
|
||||
if (!content) continue;
|
||||
const content = await storage.getItem(`assets:components:${name}.${type}`)
|
||||
if (!content) continue
|
||||
const evalStore = eval(
|
||||
`class WebComponent {}; class HTMLElement {}; (${content.toString()})`
|
||||
);
|
||||
)
|
||||
|
||||
if (isConstructor(evalStore)) {
|
||||
const className = new evalStore().constructor.name;
|
||||
const className = new evalStore().constructor.name
|
||||
|
||||
if (!classesImported.includes(className)) {
|
||||
if (
|
||||
!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";`;
|
||||
registryScript += baseClassImport;
|
||||
isBaseClassImported = true;
|
||||
const baseClassImport = `import { WebComponent, html, attachEffect } from "https://unpkg.com/web-component-base@2.0.6/index.js";`
|
||||
registryScript += baseClassImport
|
||||
isBaseClassImported = true
|
||||
}
|
||||
|
||||
registryScript += content;
|
||||
registryScript += content
|
||||
|
||||
registryScript += `customElements.define("${name}", ${className});`;
|
||||
classesImported.push(className);
|
||||
registryScript += `customElements.define("${name}", ${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) {
|
||||
try {
|
||||
new f();
|
||||
new f()
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch (err) {
|
||||
// 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}
|
||||
*/
|
||||
function doSetUp(html) {
|
||||
const ast = parse(html);
|
||||
const serverScripts = [];
|
||||
const ast = parse(html)
|
||||
const serverScripts = []
|
||||
walkSync(ast, (node) => {
|
||||
const { attributes } = node;
|
||||
const attributeKeys = Object.keys(attributes ?? {});
|
||||
const isServerScript = attributeKeys.some((key) => key.includes("server:"));
|
||||
const { attributes } = node
|
||||
const attributeKeys = Object.keys(attributes ?? {})
|
||||
const isServerScript = attributeKeys.some((key) => key.includes('server:'))
|
||||
if (
|
||||
node.type === ELEMENT_NODE &&
|
||||
node.name === "script" &&
|
||||
node.name === 'script' &&
|
||||
isServerScript
|
||||
) {
|
||||
const scripts = node.children.map((child) => child.value);
|
||||
const script = cleanScript(scripts);
|
||||
serverScripts.push(script);
|
||||
const scripts = node.children.map((child) => child.value)
|
||||
const script = cleanScript(scripts)
|
||||
serverScripts.push(script)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const setupMap = {};
|
||||
const setupMap = {}
|
||||
serverScripts.forEach((script) => {
|
||||
const { body } = parseScript(script);
|
||||
const { body } = parseScript(script)
|
||||
const keys = body
|
||||
.filter((n) => n.type === "VariableDeclaration")
|
||||
.map((n) => n["declarations"][0].id.name);
|
||||
.filter((n) => n.type === 'VariableDeclaration')
|
||||
.map((n) => n['declarations'][0].id.name)
|
||||
const constructor = `(function(){}.constructor)(\`${script}; return {${keys.join(
|
||||
","
|
||||
)}}\`);`;
|
||||
const evalStore = eval(constructor);
|
||||
Object.assign(setupMap, new evalStore());
|
||||
});
|
||||
','
|
||||
)}}\`);`
|
||||
const evalStore = eval(constructor)
|
||||
Object.assign(setupMap, new evalStore())
|
||||
})
|
||||
|
||||
const regex = /{{(.*?)}}/g;
|
||||
let match;
|
||||
const regex = /{{(.*?)}}/g
|
||||
let match
|
||||
|
||||
while ((match = regex.exec(html))) {
|
||||
let [key, value] = match;
|
||||
value = value.replace(/\s/g, "");
|
||||
let [key, value] = match
|
||||
value = value.replace(/\s/g, '')
|
||||
// nested objects
|
||||
const keys = value.split(".");
|
||||
let finalValue = "";
|
||||
let setupCopy = setupMap;
|
||||
const keys = value.split('.')
|
||||
let finalValue = ''
|
||||
let setupCopy = setupMap
|
||||
|
||||
keys.forEach((i) => {
|
||||
finalValue = setupCopy[i];
|
||||
setupCopy = finalValue;
|
||||
});
|
||||
finalValue = setupCopy[i]
|
||||
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}
|
||||
*/
|
||||
function deleteServerScripts(html) {
|
||||
const ast = parse(html);
|
||||
const ast = parse(html)
|
||||
walkSync(ast, (node) => {
|
||||
const { attributes } = node;
|
||||
const attributeKeys = Object.keys(attributes ?? {});
|
||||
const isServerScript = attributeKeys.some((key) => key.includes("server:"));
|
||||
const { attributes } = node
|
||||
const attributeKeys = Object.keys(attributes ?? {})
|
||||
const isServerScript = attributeKeys.some((key) => key.includes('server:'))
|
||||
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}
|
||||
*/
|
||||
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) {
|
||||
return (
|
||||
node.type === "Line" ||
|
||||
node.type === "Block" ||
|
||||
node.type === "BlockComment" ||
|
||||
node.type === "LineComment"
|
||||
);
|
||||
node.type === 'Line' ||
|
||||
node.type === 'Block' ||
|
||||
node.type === 'BlockComment' ||
|
||||
node.type === 'LineComment'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,25 +294,25 @@ function isComment(node) {
|
|||
* @returns {string}
|
||||
*/
|
||||
function removeComments(script) {
|
||||
const entries = [];
|
||||
const entries = []
|
||||
parseScript(script, { comment: true }, function (node, meta) {
|
||||
if (isComment(node)) {
|
||||
entries.push({
|
||||
start: meta.start.offset,
|
||||
end: meta.end.offset,
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
entries
|
||||
.sort((a, b) => {
|
||||
return b.end - a.end;
|
||||
return b.end - a.end
|
||||
})
|
||||
.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>}
|
||||
*/
|
||||
async function useFragments(html, storage) {
|
||||
const fragmentFiles = await getFiles("html", storage);
|
||||
const fragmentFiles = await getFiles('html', storage)
|
||||
|
||||
const availableFragments = fragmentFiles.reduce((acc, key) => {
|
||||
return {
|
||||
...acc,
|
||||
[key.replace(".html", "")]: "",
|
||||
};
|
||||
}, {});
|
||||
const ast = parse(html);
|
||||
[key.replace('.html', '')]: '',
|
||||
}
|
||||
}, {})
|
||||
const ast = parse(html)
|
||||
|
||||
for (const key in availableFragments) {
|
||||
/**
|
||||
* @type string | null
|
||||
*/
|
||||
let text = await storage.getItem("assets:components:" + key + ".html");
|
||||
if (!text) continue;
|
||||
availableFragments[key] = text.replace(/\n/g, "").replace(/\s+/g, " ");
|
||||
let text = await storage.getItem('assets:components:' + key + '.html')
|
||||
if (!text) continue
|
||||
availableFragments[key] = text.replace(/\n/g, '').replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
walkSync(ast, (node) => {
|
||||
const selector = Object.keys(availableFragments).find(
|
||||
(name) => name === node.name
|
||||
);
|
||||
)
|
||||
|
||||
if (node.type === ELEMENT_NODE && !!selector) {
|
||||
const index = node.parent.children.indexOf(node);
|
||||
const index = node.parent.children.indexOf(node)
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
function replaceSlots(fragmentNode, node) {
|
||||
let slotted = [];
|
||||
const containsAll = (arr, target) => target.every((v) => arr.includes(v));
|
||||
let slotted = []
|
||||
const containsAll = (arr, target) => target.every((v) => arr.includes(v))
|
||||
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
|
||||
const currentSlotName = n.attributes?.["name"] ?? null;
|
||||
let nodeChildren = [];
|
||||
const currentSlotName = n.attributes?.['name'] ?? null
|
||||
let nodeChildren = []
|
||||
|
||||
if (currentSlotName === null) {
|
||||
nodeChildren = node.children.filter(
|
||||
(child) => !child.attributes?.["slot"]
|
||||
);
|
||||
(child) => !child.attributes?.['slot']
|
||||
)
|
||||
} else {
|
||||
nodeChildren = node.children.filter((child) => {
|
||||
const childSlotName = child.attributes?.["slot"];
|
||||
return childSlotName === currentSlotName;
|
||||
});
|
||||
const childSlotName = child.attributes?.['slot']
|
||||
return childSlotName === currentSlotName
|
||||
})
|
||||
}
|
||||
|
||||
if (nodeChildren.length > 0 && !containsAll(slotted, nodeChildren)) {
|
||||
slotted = [...slotted, ...nodeChildren];
|
||||
const index = n.parent.children.indexOf(n);
|
||||
n.parent.children.splice(index, 1, ...nodeChildren);
|
||||
slotted = [...slotted, ...nodeChildren]
|
||||
const index = n.parent.children.indexOf(n)
|
||||
n.parent.children.splice(index, 1, ...nodeChildren)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -404,7 +404,7 @@ function replaceSlots(fragmentNode, node) {
|
|||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function getFiles(type, storage) {
|
||||
return (await storage.getKeys("assets:components"))
|
||||
.map((key) => key.replace("assets:components:", ""))
|
||||
.filter((key) => key.includes(type));
|
||||
return (await storage.getKeys('assets:components'))
|
||||
.map((key) => key.replace('assets:components:', ''))
|
||||
.filter((key) => key.includes(type))
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export { defineMcFlyConfig } from "./define-config.js";
|
||||
export { useMcFlyRoute } from "./event-handler.js";
|
||||
export { defineMcFlyConfig } from './define-config.js'
|
||||
export { useMcFlyRoute } from './event-handler.js'
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { consola } from "consola";
|
||||
import { colorize } from "consola/utils";
|
||||
import { downloadTemplate } from "giget";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { consola } from 'consola'
|
||||
import { colorize } from 'consola/utils'
|
||||
import { downloadTemplate } from 'giget'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
|
||||
const [, , directoryArg] = process.argv;
|
||||
const [, , directoryArg] = process.argv
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
|
@ -23,25 +23,25 @@ const [, , directoryArg] = process.argv;
|
|||
* Create McFly App
|
||||
*/
|
||||
async function create() {
|
||||
const defaultDirectory = "mcfly-app";
|
||||
consola.box(`Hello! Welcome to ${colorize("bold", "McFly")}!`);
|
||||
let directory = directoryArg;
|
||||
const defaultDirectory = 'mcfly-app'
|
||||
consola.box(`Hello! Welcome to ${colorize('bold', 'McFly')}!`)
|
||||
let directory = directoryArg
|
||||
|
||||
if (!directory) {
|
||||
directory =
|
||||
(await consola.prompt("Give your new project a name:", {
|
||||
(await consola.prompt('Give your new project a name:', {
|
||||
placeholder: defaultDirectory,
|
||||
})) ?? defaultDirectory;
|
||||
})) ?? defaultDirectory
|
||||
} else {
|
||||
consola.success(`Using ${directory} as name.`);
|
||||
consola.success(`Using ${directory} as name.`)
|
||||
}
|
||||
|
||||
if (typeof directory !== "string") {
|
||||
return;
|
||||
if (typeof directory !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
directory = getSafeDirectory(directory);
|
||||
const hasErrors = await downloadTemplateToDirectory(directory);
|
||||
directory = getSafeDirectory(directory)
|
||||
const hasErrors = await downloadTemplateToDirectory(directory)
|
||||
|
||||
if (!hasErrors) {
|
||||
/**
|
||||
|
@ -50,33 +50,33 @@ async function create() {
|
|||
const prompts = [
|
||||
{
|
||||
prompt: `Would you like to install the dependencies to ${colorize(
|
||||
"bold",
|
||||
'bold',
|
||||
directory
|
||||
)}?`,
|
||||
info: "This might take some time depending on your connectivity.",
|
||||
startMessage: "Installing dependencies using npm...",
|
||||
info: 'This might take some time depending on your connectivity.',
|
||||
startMessage: 'Installing dependencies using npm...',
|
||||
command: `npm`,
|
||||
subCommand: "install",
|
||||
subCommand: 'install',
|
||||
error: `Install dependencies later with ${colorize(
|
||||
"yellow",
|
||||
"npm install"
|
||||
'yellow',
|
||||
'npm install'
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
prompt: "Would you like to initialize your git repository?",
|
||||
startMessage: "Initializing git repository...",
|
||||
prompt: 'Would you like to initialize your git repository?',
|
||||
startMessage: 'Initializing git repository...',
|
||||
command: `git`,
|
||||
subCommand: "init",
|
||||
subCommand: 'init',
|
||||
error: `Initialize git repository later with ${colorize(
|
||||
"yellow",
|
||||
"git init"
|
||||
'yellow',
|
||||
'git init'
|
||||
)}`,
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const intentions = await askPrompts(prompts, directory);
|
||||
const intentions = await askPrompts(prompts, directory)
|
||||
if (!!intentions && intentions.length > 0)
|
||||
showResults(directory, intentions[0]);
|
||||
showResults(directory, intentions[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,10 +86,10 @@ async function create() {
|
|||
* @returns string | undefined
|
||||
*/
|
||||
function getSafeDirectory(directory) {
|
||||
const { platform } = process;
|
||||
const locale = path[platform === `win32` ? `win32` : `posix`];
|
||||
const localePath = directory.split(path.sep).join(locale.sep);
|
||||
return path.normalize(localePath);
|
||||
const { platform } = process
|
||||
const locale = path[platform === `win32` ? `win32` : `posix`]
|
||||
const localePath = directory.split(path.sep).join(locale.sep)
|
||||
return path.normalize(localePath)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,22 +98,22 @@ function getSafeDirectory(directory) {
|
|||
* @returns Promise<boolean> hasErrors
|
||||
*/
|
||||
async function downloadTemplateToDirectory(directory) {
|
||||
let hasErrors = false;
|
||||
let hasErrors = false
|
||||
|
||||
try {
|
||||
consola.start(
|
||||
`Copying template to ${colorize("bold", getSafeDirectory(directory))}...`
|
||||
);
|
||||
await downloadTemplate("github:ayoayco/mcfly/templates/basic", {
|
||||
`Copying template to ${colorize('bold', getSafeDirectory(directory))}...`
|
||||
)
|
||||
await downloadTemplate('github:ayoayco/mcfly/templates/basic', {
|
||||
dir: directory,
|
||||
});
|
||||
})
|
||||
} catch (ㆆ_ㆆ) {
|
||||
consola.error(ㆆ_ㆆ.message);
|
||||
consola.info("Try a different name.\n");
|
||||
hasErrors = true;
|
||||
consola.error(ㆆ_ㆆ.message)
|
||||
consola.info('Try a different name.\n')
|
||||
hasErrors = true
|
||||
}
|
||||
|
||||
return hasErrors;
|
||||
return hasErrors
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,37 +123,37 @@ async function downloadTemplateToDirectory(directory) {
|
|||
* @returns Array<boolean> | undefined
|
||||
*/
|
||||
async function askPrompts(prompts, cwd) {
|
||||
const results = [];
|
||||
const results = []
|
||||
|
||||
for (const p of prompts) {
|
||||
const userIntends = await consola.prompt(p.prompt, {
|
||||
type: "confirm",
|
||||
});
|
||||
type: 'confirm',
|
||||
})
|
||||
|
||||
if (typeof userIntends !== "boolean") {
|
||||
return;
|
||||
if (typeof userIntends !== 'boolean') {
|
||||
return
|
||||
}
|
||||
|
||||
if (userIntends) {
|
||||
p.info && consola.info(p.info);
|
||||
consola.start(p.startMessage);
|
||||
p.info && consola.info(p.info)
|
||||
consola.start(p.startMessage)
|
||||
try {
|
||||
spawnSync(p.command, [p.subCommand], {
|
||||
cwd,
|
||||
shell: true,
|
||||
timeout: 100_000,
|
||||
stdio: "inherit",
|
||||
});
|
||||
consola.success("Done!");
|
||||
stdio: 'inherit',
|
||||
})
|
||||
consola.success('Done!')
|
||||
} catch (ㆆ_ㆆ) {
|
||||
consola.error(ㆆ_ㆆ.message);
|
||||
consola.info(p.error + "\n");
|
||||
consola.error(ㆆ_ㆆ.message)
|
||||
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) {
|
||||
let nextActions = [
|
||||
`Go to your project by running ${colorize("yellow", `cd ${directory}`)}`,
|
||||
];
|
||||
`Go to your project by running ${colorize('yellow', `cd ${directory}`)}`,
|
||||
]
|
||||
|
||||
if (!installDeps) {
|
||||
nextActions.push(
|
||||
`Install the dependencies with ${colorize("yellow", "npm install")}`
|
||||
);
|
||||
`Install the dependencies with ${colorize('yellow', 'npm install')}`
|
||||
)
|
||||
}
|
||||
|
||||
nextActions = nextActions.concat([
|
||||
`Start the dev server with ${colorize("yellow", "npm start")}`,
|
||||
`Join us at ${colorize("blue", "https://ayco.io/gh/McFly")}`,
|
||||
]);
|
||||
`Start the dev server with ${colorize('yellow', 'npm start')}`,
|
||||
`Join us at ${colorize('blue', 'https://ayco.io/gh/McFly')}`,
|
||||
])
|
||||
|
||||
const result = `🎉 Your new ${colorize(
|
||||
"bold",
|
||||
"McFly"
|
||||
'bold',
|
||||
'McFly'
|
||||
)} app is ready: ${directory}\n\nNext actions: ${nextActions
|
||||
.map((action, index) => `\n${++index}. ${action}`)
|
||||
.join("")}`;
|
||||
.join('')}`
|
||||
|
||||
consola.box(result);
|
||||
consola.box(result)
|
||||
}
|
||||
|
||||
create();
|
||||
create()
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const config = {
|
||||
trailingComma: "es5",
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 2,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defineMcFlyConfig } from "#imports";
|
||||
import { defineMcFlyConfig } from '#imports'
|
||||
export default defineMcFlyConfig({
|
||||
components: "js",
|
||||
});
|
||||
components: 'js',
|
||||
})
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
export default defineNitroConfig({
|
||||
extends: "@mcflyjs/config",
|
||||
extends: '@mcflyjs/config',
|
||||
|
||||
devServer: {
|
||||
watch: ["../packages"],
|
||||
watch: ['../packages'],
|
||||
},
|
||||
|
||||
routeRules: {
|
||||
"/chat": {
|
||||
'/chat': {
|
||||
redirect: {
|
||||
to: "https://matrix.to/#/#mcfly:matrix.org",
|
||||
to: 'https://matrix.to/#/#mcfly:matrix.org',
|
||||
statusCode: 302,
|
||||
},
|
||||
},
|
||||
|
@ -19,5 +19,5 @@ export default defineNitroConfig({
|
|||
brotli: true,
|
||||
},
|
||||
|
||||
compatibilityDate: "2024-12-08",
|
||||
});
|
||||
compatibilityDate: '2024-12-08',
|
||||
})
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,5 +5,5 @@
|
|||
* ...reusable code are in ./src/components
|
||||
* @see https://ayco.io/gh/McFly#special-directories
|
||||
*/
|
||||
import config from "../mcfly.config.mjs";
|
||||
export default useMcFlyRoute({ config, storage: useStorage() });
|
||||
import config from '../mcfly.config.mjs'
|
||||
export default useMcFlyRoute({ config, storage: useStorage() })
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
*/
|
||||
class ClickableText extends WebComponent {
|
||||
onInit() {
|
||||
this.onclick = () => alert("Thank you for clicking the text!");
|
||||
this.onclick = () => alert('Thank you for clicking the text!')
|
||||
}
|
||||
get template() {
|
||||
return `<span style="cursor:pointer">Click me too!</span>`;
|
||||
return `<span style="cursor:pointer">Click me too!</span>`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
class CodeBlockComponent extends HTMLElement {
|
||||
connectedCallback() {
|
||||
const trimmed = this.innerHTML.trim();
|
||||
const lang = this.getAttribute("language");
|
||||
const inline = this.getAttribute("inline") !== null;
|
||||
const trimmed = this.innerHTML.trim()
|
||||
const lang = this.getAttribute('language')
|
||||
const inline = this.getAttribute('inline') !== null
|
||||
|
||||
this.innerHTML = `
|
||||
<pre id="pre"><code id="code">${trimmed}</code></pre>
|
||||
`;
|
||||
`
|
||||
|
||||
/**
|
||||
* @type {HTMLPreElement}
|
||||
*/
|
||||
const pre = this.querySelector("#pre");
|
||||
const pre = this.querySelector('#pre')
|
||||
|
||||
if (lang) {
|
||||
pre.className = `language-${lang}`;
|
||||
pre.className = `language-${lang}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Partial<CSSStyleDeclaration>}
|
||||
*/
|
||||
const style = {
|
||||
background: "#f5f2f0",
|
||||
padding: "1em",
|
||||
margin: "1em 0",
|
||||
fontSize: "large",
|
||||
overflow: "auto",
|
||||
borderRadius: '5px'
|
||||
};
|
||||
background: '#f5f2f0',
|
||||
padding: '1em',
|
||||
margin: '1em 0',
|
||||
fontSize: 'large',
|
||||
overflow: 'auto',
|
||||
borderRadius: '5px',
|
||||
}
|
||||
|
||||
if (inline) {
|
||||
style.display = 'inline';
|
||||
style.padding = '0.3em';
|
||||
style.display = 'inline'
|
||||
style.padding = '0.3em'
|
||||
}
|
||||
|
||||
Object.keys(style).forEach((rule) => {
|
||||
pre.style[rule] = style[rule];
|
||||
});
|
||||
pre.style[rule] = style[rule]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,16 @@ class HelloWorld extends WebComponent {
|
|||
count: 0,
|
||||
}
|
||||
|
||||
updateLabel(){
|
||||
this.props.myName = `Clicked ${++this.props.count}x`;
|
||||
updateLabel() {
|
||||
this.props.myName = `Clicked ${++this.props.count}x`
|
||||
}
|
||||
|
||||
|
||||
get template() {
|
||||
return html`
|
||||
<button onClick=${() => this.updateLabel()} style="cursor:pointer">
|
||||
Hello ${this.props.myName}!
|
||||
</button>`;
|
||||
return html` <button
|
||||
onClick=${() => this.updateLabel()}
|
||||
style="cursor:pointer"
|
||||
>
|
||||
Hello ${this.props.myName}!
|
||||
</button>`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
class HelloWorld extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ["my-name"];
|
||||
return ['my-name']
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
let count = 0;
|
||||
const currentName = this.getAttribute('my-name');
|
||||
let count = 0
|
||||
const currentName = this.getAttribute('my-name')
|
||||
|
||||
if (!currentName) {
|
||||
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) {
|
||||
if (property === "my-name" && previousValue !== currentValue) {
|
||||
this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`;
|
||||
if (property === 'my-name' && previousValue !== currentValue) {
|
||||
this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defineMcFlyConfig } from "#imports";
|
||||
import { defineMcFlyConfig } from '#imports'
|
||||
export default defineMcFlyConfig({
|
||||
components: "js",
|
||||
});
|
||||
components: 'js',
|
||||
})
|
||||
|
|
|
@ -1 +1 @@
|
|||
export default defineNitroConfig({ extends: '@mcflyjs/config' });
|
||||
export default defineNitroConfig({ extends: '@mcflyjs/config' })
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
* ...reusable code are in ./src/components
|
||||
* @see https://ayco.io/gh/McFly#special-directories
|
||||
*/
|
||||
import config from "../mcfly.config.mjs";
|
||||
export default useMcFlyRoute({ config, storage: useStorage() });
|
||||
import config from '../mcfly.config.mjs'
|
||||
export default useMcFlyRoute({ config, storage: useStorage() })
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
export default eventHandler(() => {
|
||||
return {
|
||||
user: "AYO",
|
||||
user: 'AYO',
|
||||
date: new Date(),
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
class CodeBlockComponent extends HTMLElement {
|
||||
connectedCallback() {
|
||||
const trimmed = this.innerHTML.trim();
|
||||
const lang = this.getAttribute("language");
|
||||
const inline = this.getAttribute("inline") !== null;
|
||||
const trimmed = this.innerHTML.trim()
|
||||
const lang = this.getAttribute('language')
|
||||
const inline = this.getAttribute('inline') !== null
|
||||
|
||||
this.innerHTML = `
|
||||
<pre id="pre"><code id="code">${trimmed}</code></pre>
|
||||
`;
|
||||
`
|
||||
|
||||
/**
|
||||
* @type {HTMLPreElement}
|
||||
*/
|
||||
const pre = this.querySelector("#pre");
|
||||
const pre = this.querySelector('#pre')
|
||||
|
||||
if (lang) {
|
||||
pre.className = `language-${lang}`;
|
||||
pre.className = `language-${lang}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Partial<CSSStyleDeclaration>}
|
||||
*/
|
||||
const style = {
|
||||
background: "#f5f2f0",
|
||||
padding: "1em",
|
||||
margin: "1em 0",
|
||||
fontSize: "large",
|
||||
overflow: "auto",
|
||||
borderRadius: '5px'
|
||||
};
|
||||
background: '#f5f2f0',
|
||||
padding: '1em',
|
||||
margin: '1em 0',
|
||||
fontSize: 'large',
|
||||
overflow: 'auto',
|
||||
borderRadius: '5px',
|
||||
}
|
||||
|
||||
if (inline) {
|
||||
style.display = 'inline';
|
||||
style.padding = '0.3em';
|
||||
style.display = 'inline'
|
||||
style.padding = '0.3em'
|
||||
}
|
||||
|
||||
Object.keys(style).forEach((rule) => {
|
||||
pre.style[rule] = style[rule];
|
||||
});
|
||||
pre.style[rule] = style[rule]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
class MyHelloWorld extends WebComponent {
|
||||
static props = {
|
||||
myName: 'World',
|
||||
count: 0
|
||||
count: 0,
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
|
@ -13,9 +13,11 @@ class MyHelloWorld extends WebComponent {
|
|||
}
|
||||
|
||||
get template() {
|
||||
return html`
|
||||
<button onClick=${() => this.updateLabel()} style="cursor:pointer">
|
||||
Hello ${this.props.myName}!
|
||||
</button>`;
|
||||
return html` <button
|
||||
onClick=${() => this.updateLabel()}
|
||||
style="cursor:pointer"
|
||||
>
|
||||
Hello ${this.props.myName}!
|
||||
</button>`
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue