From f08ce4c04373369143fb8ce5911baf7fcc76814b Mon Sep 17 00:00:00 2001 From: Ayo Ayco Date: Wed, 8 Jan 2025 20:03:19 +0100 Subject: [PATCH] feat: integration hooks system (#54) * refactor: modularize event-handler transform functions * feat: add plugin hooks from mcfly config --- packages/cli/commands/build.mjs | 1 + packages/cli/commands/serve.mjs | 4 + packages/core/default-mcfly-config.js | 3 + packages/core/evaluate-scripts.js | 162 ++++++++++ packages/core/event-handler.js | 421 ++++---------------------- packages/core/get-files.js | 11 + packages/core/hooks.js | 6 + packages/core/inject-elements.js | 108 +++++++ packages/core/inject-fragments.js | 88 ++++++ packages/core/package.json | 7 +- pnpm-lock.yaml | 39 +-- site/mcfly.config.mjs | 8 + site/package.json | 3 +- 13 files changed, 458 insertions(+), 403 deletions(-) create mode 100644 packages/core/default-mcfly-config.js create mode 100644 packages/core/evaluate-scripts.js create mode 100644 packages/core/get-files.js create mode 100644 packages/core/hooks.js create mode 100644 packages/core/inject-elements.js create mode 100644 packages/core/inject-fragments.js diff --git a/packages/cli/commands/build.mjs b/packages/cli/commands/build.mjs index 4009141..0395caf 100644 --- a/packages/cli/commands/build.mjs +++ b/packages/cli/commands/build.mjs @@ -29,6 +29,7 @@ async function _build(args) { ...(mcflyConfig.nitro ?? {}), ...(nitroConfig ?? {}), }) + nitro.options.runtimeConfig.mcfly = mcflyConfig await prepare(nitro) await copyPublicAssets(nitro) await prerender(nitro) diff --git a/packages/cli/commands/serve.mjs b/packages/cli/commands/serve.mjs index 5e23dde..ee8fd56 100644 --- a/packages/cli/commands/serve.mjs +++ b/packages/cli/commands/serve.mjs @@ -46,6 +46,7 @@ async function serve(args) { */ let nitro const reload = async () => { + // close existing nitro if (nitro) { consola.info('Restarting dev server...') if ('unwatch' in nitro.options._c12) { @@ -53,6 +54,8 @@ async function serve(args) { } await nitro.close() } + + // create new nitro nitro = await createNitro( { extends: '@mcflyjs/config', @@ -87,6 +90,7 @@ async function serve(args) { } ) nitro.hooks.hookOnce('restart', reload) + nitro.options.runtimeConfig.mcfly = mcflyConfig const server = createDevServer(nitro) // const listenOptions = parseArgs(args) await server.listen(1234) diff --git a/packages/core/default-mcfly-config.js b/packages/core/default-mcfly-config.js new file mode 100644 index 0000000..ccedefe --- /dev/null +++ b/packages/core/default-mcfly-config.js @@ -0,0 +1,3 @@ +export default { + components: 'js', +} diff --git a/packages/core/evaluate-scripts.js b/packages/core/evaluate-scripts.js new file mode 100644 index 0000000..243a988 --- /dev/null +++ b/packages/core/evaluate-scripts.js @@ -0,0 +1,162 @@ +import { ELEMENT_NODE, parse, renderSync, walkSync } from 'ultrahtml' +import { parseScript } from 'esprima' +import { consola } from 'consola' + +/** + * @typedef {import('estree').BaseNode} JsNode + */ + +/** + * McFly HTML Template parser + * @param {*} _html + * @returns {string} + */ +export function evaluateServerScripts(_html) { + let html = evaluateServerScript(_html) + html = deleteServerScripts(html) + return html +} + +/** + * Evaluates server:setup script and replaces all variables used in the HTML + * @param {string} html + * @returns {string} + */ +function evaluateServerScript(html) { + 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:')) + if ( + node.type === ELEMENT_NODE && + node.name === 'script' && + isServerScript + ) { + const scripts = node.children.map((child) => child.value) + const script = cleanScript(scripts) + serverScripts.push(script) + } + }) + + const setupMap = {} + serverScripts.forEach((script) => { + const { body } = parseScript(script) + const keys = body + .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 regex = /\{\{(.*?)\}\}/g + let match + + while ((match = regex.exec(html))) { + let [key, rawValue] = match + + const value = rawValue.replace(/\s/g, '') + const keys = value.split('.') + let finalValue = '' + let setupCopy = setupMap + + // if not in the server script, it could be a js expression + if (!(keys[0] in setupMap)) { + try { + finalValue = eval(rawValue) + } catch (e) { + consola.error('[ERR]: Failed to evaluate expression', e) + } + } + + // nested objects + keys.forEach((key) => { + if (key in setupCopy) { + finalValue = setupCopy[key] + setupCopy = finalValue + } + }) + + html = html.replace(key, finalValue ?? '') + regex.lastIndex = -1 + } + + return html +} + +/** + * Removes any instance of server:setup script in the HTML + * @param {string} html + * @returns {string} + */ +function deleteServerScripts(html) { + const ast = parse(html) + walkSync(ast, (node) => { + 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) + } + }) + + return renderSync(ast) +} + +/** + * Cleans a JS string for save evaluation + * @param {Array} scripts + * @returns {string} + */ +function cleanScript(scripts) { + let script = scripts.map((s) => s.trim()).join(' ') + + script = removeComments(script) + + return script //.replace(/\n/g, '').replace(/\s+/g, ' ') +} + +/** + * Removes all instances of comments in a JS string + * @param {string} script + * @returns {string} + */ +function removeComments(script) { + 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 + }) + .forEach((n) => { + script = script.slice(0, n.start) + script.slice(n.end) + }) + + return script +} + +/** + * Checks if given node of a JS script is a comment + * @param {JsNode} node + * @returns {boolean} + */ +function isComment(node) { + return ( + node.type === 'Line' || + node.type === 'Block' || + node.type === 'BlockComment' || + node.type === 'LineComment' + ) +} diff --git a/packages/core/event-handler.js b/packages/core/event-handler.js index 0bdace9..e488c26 100644 --- a/packages/core/event-handler.js +++ b/packages/core/event-handler.js @@ -1,16 +1,17 @@ -import { ELEMENT_NODE, parse, render, renderSync, walkSync } from 'ultrahtml' import { consola } from 'consola' -import { parseScript } from 'esprima' -import { loadConfig } from 'c12' import { eventHandler } from 'h3' -import { resolve } from 'pathe' +import { createHooks } from 'hookable' +import { hooks as mcflyHooks } from './hooks.js' +import { evaluateServerScripts } from './evaluate-scripts.js' +import { injectHtmlFragments } from './inject-fragments.js' +import { injectCustomElements } from './inject-elements.js' +import defaultMcflyConfig from './default-mcfly-config.js' +import { loadConfig } from 'c12' /** * @typedef {import('../config').McFlyConfig} Config * @typedef {import('unstorage').Storage} Storage * @typedef {import('unstorage').StorageValue} StorageValue - * @typedef {import('ultrahtml').Node} HtmlNode - * @typedef {import('estree').BaseNode} JsNode * @typedef {import('h3').EventHandler} EventHandler */ @@ -22,35 +23,58 @@ import { resolve } from 'pathe' * @returns {EventHandler} */ export function useMcFlyRoute({ storage }) { + const hooks = createHooks() + Object.keys(mcflyHooks).forEach((hookName) => hooks.addHooks(hookName)) + return eventHandler(async (event) => { const { path } = event - const rootDir = resolve('.') - const loadedConfig = await loadConfig({ - name: 'mcfly', - configFile: 'mcfly.config', - cwd: rootDir, - }) - const config = { - components: 'js', // work around for c12.loadConfig not working on Netlify function - ...loadedConfig.config, + let { config } = await loadConfig({ name: 'mcfly' }) + + if (!config || Object.keys(config).length === 0) { + config = defaultMcflyConfig + consola.warn(`[WARN]: McFly configuration not loaded, using defaults...`) } + + const plugins = config.plugins + + plugins.forEach((plugin) => { + const pluginHooks = Object.keys(plugin) + pluginHooks.forEach((pluginHook) => { + hooks.hook(pluginHook, plugin[pluginHook]) + }) + }) + const { components: componentType } = config let html = await getHtml(path, storage) - consola.info('[INFO]: Config found\n', config) - consola.info('>>> Current Working Directory: ', rootDir) - if (html) { - const transforms = [evaluateServerScript, deleteServerScripts] - - for (const transform of transforms) { - html = transform(html.toString()) - } - - html = await useFragments(html.toString(), storage) + const transforms = [ + { + fn: evaluateServerScripts, + args: [''], + hook: mcflyHooks.serverScriptsEvaluated, + }, + { + fn: injectHtmlFragments, + args: [storage], + hook: mcflyHooks.fragmentsInjected, + }, + { + fn: injectCustomElements, + args: [componentType, storage], + hook: mcflyHooks.customElementsInjected, + }, + ] if (!!componentType && !!html) { - html = await insertRegistry(html.toString(), componentType, storage) + for (const transform of transforms) { + html = await transform.fn(html.toString(), ...transform.args) + + // call hook + if (transform.hook) { + hooks.callHook(transform.hook) + } + } } else { consola.error('[ERR]: Failed to insert registry', { componentType: !componentType ? 'missing' : 'okay', @@ -59,6 +83,10 @@ export function useMcFlyRoute({ storage }) { } } + if (html) { + hooks.callHook(mcflyHooks.pageRendered) + } + return ( html ?? new Response( @@ -72,7 +100,6 @@ export function useMcFlyRoute({ storage }) { function getPurePath(path) { return path.split('?')[0] } - /** * Gets the correct HTML depending on the path requested * @param {string} path @@ -101,343 +128,3 @@ async function getHtml(path, storage) { function getPath(filename) { return `assets:pages${filename}` } - -/** - * Returns transformed HTML with custom elements registry in the head - * @param {string} html - * @param {Config['components']} type - * @param {Storage} storage - * @returns {Promise} - */ -async function insertRegistry(html, type, storage) { - const ast = parse(html) - const componentFiles = await getFiles(type, storage) - const availableComponents = componentFiles.map((key) => - key.replace(`.${type}`, '') - ) - - const usedCustomElements = [] - - walkSync(ast, (node) => { - const usedElement = availableComponents.find((name) => name === node.name) - - if (node.type === ELEMENT_NODE && !!usedElement) { - usedCustomElements.push(usedElement) - } - }) - - // insert registry script to head - if (usedCustomElements.length > 0) { - const registryScript = await buildRegistry( - usedCustomElements, - type, - storage - ) - walkSync(ast, (node) => { - if (node.type === ELEMENT_NODE && node.name === 'head') { - node.children.push(parse(registryScript)) - } - }) - } - - return render(ast) -} - -/** - * Builds the string containing all custom elements definition - * @param {Array} usedCustomElements - * @param {Config['components']} type - * @param {Storage} storage - * @returns {Promise} - */ -async function buildRegistry(usedCustomElements, type, storage) { - let registryScript = `' - - return registryScript -} - -/** - * Check if function is a constructor - * @param {function} f - * @returns boolean - */ -function isConstructor(f) { - try { - new f() - // eslint-disable-next-line no-unused-vars - } catch (err) { - // TODO: verify err is the expected error and then - return false - } - return true -} - -/** - * Evaluates server:setup script and replaces all variables used in the HTML - * @param {string} html - * @returns {string} - */ -function evaluateServerScript(html) { - 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:')) - if ( - node.type === ELEMENT_NODE && - node.name === 'script' && - isServerScript - ) { - const scripts = node.children.map((child) => child.value) - const script = cleanScript(scripts) - serverScripts.push(script) - } - }) - - const setupMap = {} - serverScripts.forEach((script) => { - const { body } = parseScript(script) - const keys = body - .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 regex = /\{\{(.*?)\}\}/g - let match - - while ((match = regex.exec(html))) { - let [key, rawValue] = match - - const value = rawValue.replace(/\s/g, '') - const keys = value.split('.') - let finalValue = '' - let setupCopy = setupMap - - // if not in the server script, it could be a js expression - if (!(keys[0] in setupMap)) { - try { - finalValue = eval(rawValue) - } catch (e) { - consola.error('[ERR]: Failed to evaluate expression', e) - } - } - - // nested objects - keys.forEach((key) => { - if (key in setupCopy) { - finalValue = setupCopy[key] - setupCopy = finalValue - } - }) - - html = html.replace(key, finalValue ?? '') - regex.lastIndex = -1 - } - - return html -} - -/** - * Removes any instance of server:setup script in the HTML - * @param {string} html - * @returns {string} - */ -function deleteServerScripts(html) { - const ast = parse(html) - walkSync(ast, (node) => { - 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) - } - }) - - return renderSync(ast) -} - -/** - * Cleans a JS string for save evaluation - * @param {Array} scripts - * @returns {string} - */ -function cleanScript(scripts) { - let script = scripts.map((s) => s.trim()).join(' ') - - script = removeComments(script) - - return script //.replace(/\n/g, '').replace(/\s+/g, ' ') -} - -/** - * Checks if given node of a JS script is a comment - * @param {JsNode} node - * @returns {boolean} - */ -function isComment(node) { - return ( - node.type === 'Line' || - node.type === 'Block' || - node.type === 'BlockComment' || - node.type === 'LineComment' - ) -} - -/** - * Removes all instances of comments in a JS string - * @param {string} script - * @returns {string} - */ -function removeComments(script) { - 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 - }) - .forEach((n) => { - script = script.slice(0, n.start) + script.slice(n.end) - }) - - return script -} - -/** - * Return HTML with all fragments replaced with the correct content in the storage - * @param {string} html - * @param {Storage} storage - * @returns {Promise} - */ -async function useFragments(html, storage) { - const fragmentFiles = await getFiles('html', storage) - - const availableFragments = fragmentFiles.reduce((acc, key) => { - return { - ...acc, - [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, ' ') - } - - 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) - /** - * @type {HtmlNode} - */ - const fragmentNode = parse(availableFragments[selector]) - - replaceSlots(fragmentNode, node) - - node.parent.children[index] = fragmentNode - } - }) - - return render(ast) -} - -/** - * Replace a slot in a fragmentNode with given node - * @param {HtmlNode} fragmentNode - * @param {HtmlNode} node - * @returns {void} - */ -function replaceSlots(fragmentNode, node) { - let slotted = [] - const containsAll = (arr, target) => target.every((v) => arr.includes(v)) - walkSync(fragmentNode, (n) => { - if (n.type === ELEMENT_NODE && n.name === 'slot') { - // find node child with same name attribute - const currentSlotName = n.attributes?.['name'] ?? null - let nodeChildren = [] - - if (currentSlotName === null) { - nodeChildren = node.children.filter( - (child) => !child.attributes?.['slot'] - ) - } else { - nodeChildren = node.children.filter((child) => { - 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) - } - } - }) -} - -/** - * Get all files from the storage given a type - * @param {string} type - * @param {Storage} storage - * @returns {Promise} - */ -async function getFiles(type, storage) { - return (await storage.getKeys('assets:components')) - .map((key) => key.replace('assets:components:', '')) - .filter((key) => key.includes(type)) -} diff --git a/packages/core/get-files.js b/packages/core/get-files.js new file mode 100644 index 0000000..2141d01 --- /dev/null +++ b/packages/core/get-files.js @@ -0,0 +1,11 @@ +/** + * Get all files from the storage given a type + * @param {string} type + * @param {Storage} storage + * @returns {Promise} + */ +export async function getFiles(type, storage) { + return (await storage.getKeys('assets:components')) + .map((key) => key.replace('assets:components:', '')) + .filter((key) => key.includes(type)) +} diff --git a/packages/core/hooks.js b/packages/core/hooks.js new file mode 100644 index 0000000..7e682cd --- /dev/null +++ b/packages/core/hooks.js @@ -0,0 +1,6 @@ +export const hooks = { + pageRendered: 'mcfly:page:rendered', + serverScriptsEvaluated: 'mcfly:scripts:evaluated', + fragmentsInjected: 'mcfly:fragments:injected', + customElementsInjected: 'mcfly:elements:injected', +} diff --git a/packages/core/inject-elements.js b/packages/core/inject-elements.js new file mode 100644 index 0000000..0dc7aa4 --- /dev/null +++ b/packages/core/inject-elements.js @@ -0,0 +1,108 @@ +import { ELEMENT_NODE, parse, render, walkSync } from 'ultrahtml' +import { getFiles } from './get-files.js' + +/** + * @typedef {import('../config').McFlyConfig} Config + */ + +/** + * Returns transformed HTML with custom elements registry in the head + * @param {string} html + * @param {Config['components']} type + * @param {Storage} storage + * @returns {Promise} + */ +export async function injectCustomElements(html, type, storage) { + const ast = parse(html) + const componentFiles = await getFiles(type, storage) + const availableComponents = componentFiles.map((key) => + key.replace(`.${type}`, '') + ) + + const usedCustomElements = [] + + walkSync(ast, (node) => { + const usedElement = availableComponents.find((name) => name === node.name) + + if (node.type === ELEMENT_NODE && !!usedElement) { + usedCustomElements.push(usedElement) + } + }) + + // insert registry script to head + if (usedCustomElements.length > 0) { + const registryScript = await buildRegistry( + usedCustomElements, + type, + storage + ) + walkSync(ast, (node) => { + if (node.type === ELEMENT_NODE && node.name === 'head') { + node.children.push(parse(registryScript)) + } + }) + } + + return render(ast) +} + +/** + * Builds the string containing all custom elements definition + * @param {Array} usedCustomElements + * @param {Config['components']} type + * @param {Storage} storage + * @returns {Promise} + */ +async function buildRegistry(usedCustomElements, type, storage) { + let registryScript = `' + + return registryScript +} + +/** + * Check if function is a constructor + * @param {function} f + * @returns boolean + */ +function isConstructor(f) { + try { + new f() + // eslint-disable-next-line no-unused-vars + } catch (err) { + // TODO: verify err is the expected error and then + return false + } + return true +} diff --git a/packages/core/inject-fragments.js b/packages/core/inject-fragments.js new file mode 100644 index 0000000..901ae0d --- /dev/null +++ b/packages/core/inject-fragments.js @@ -0,0 +1,88 @@ +import { ELEMENT_NODE, parse, render, walkSync } from 'ultrahtml' +import { getFiles } from './get-files.js' + +/** + * @typedef {import('ultrahtml').Node} HtmlNode + */ + +/** + * Return HTML with all fragments replaced with the correct content in the storage + * @param {string} html + * @param {Storage} storage + * @returns {Promise} + */ +export async function injectHtmlFragments(html, storage) { + const fragmentFiles = await getFiles('html', storage) + + const availableFragments = fragmentFiles.reduce((acc, key) => { + return { + ...acc, + [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, ' ') + } + + 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) + /** + * @type {HtmlNode} + */ + const fragmentNode = parse(availableFragments[selector]) + + replaceSlots(fragmentNode, node) + + node.parent.children[index] = fragmentNode + } + }) + + return render(ast) +} + +/** + * Replace a slot in a fragmentNode with given node + * @param {HtmlNode} fragmentNode + * @param {HtmlNode} node + * @returns {void} + */ +function replaceSlots(fragmentNode, node) { + let slotted = [] + const containsAll = (arr, target) => target.every((v) => arr.includes(v)) + walkSync(fragmentNode, (n) => { + if (n.type === ELEMENT_NODE && n.name === 'slot') { + // find node child with same name attribute + const currentSlotName = n.attributes?.['name'] ?? null + let nodeChildren = [] + + if (currentSlotName === null) { + nodeChildren = node.children.filter( + (child) => !child.attributes?.['slot'] + ) + } else { + nodeChildren = node.children.filter((child) => { + 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) + } + } + }) +} diff --git a/packages/core/package.json b/packages/core/package.json index 15fb1a3..87cff4d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -25,12 +25,13 @@ "consola": "^3.3.3", "esbuild": "^0.24.2", "esprima": "^4.0.1", - "h3": "^1.8.2", - "nitropack": "~2.10.4", + "h3": "^1.13.0", + "nitropack": "latest", "pathe": "^1.1.2", - "ultrahtml": "^1.5.2" + "ultrahtml": "^1.5.3" }, "devDependencies": { + "hookable": "^5.5.3", "unstorage": "^1.14.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d065572..eb66e23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,18 +95,21 @@ importers: specifier: ^4.0.1 version: 4.0.1 h3: - specifier: ^1.8.2 + specifier: ^1.13.0 version: 1.13.0 nitropack: - specifier: ~2.10.4 + specifier: latest version: 2.10.4(typescript@5.7.2) pathe: specifier: ^1.1.2 version: 1.1.2 ultrahtml: - specifier: ^1.5.2 + specifier: ^1.5.3 version: 1.5.3 devDependencies: + hookable: + specifier: ^5.5.3 + version: 5.5.3 unstorage: specifier: ^1.14.4 version: 1.14.4(db0@0.2.1)(ioredis@5.4.2) @@ -131,18 +134,15 @@ importers: '@mcflyjs/core': specifier: workspace:* version: link:../packages/core - nitropack: - specifier: ~2.10.4 - version: 2.10.4(typescript@5.7.2) templates/basic: dependencies: '@mcflyjs/cli': specifier: ^0.1.1 - version: 0.1.1(magicast@0.3.5) + version: link:../../packages/cli '@mcflyjs/config': specifier: ^0.2.1 - version: 0.2.1 + version: link:../../packages/config '@mcflyjs/core': specifier: ^0.6.1 version: 0.6.1(magicast@0.3.5)(typescript@5.7.2) @@ -622,13 +622,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@mcflyjs/cli@0.1.1': - resolution: {integrity: sha512-YT0XVwbUO9SscfoCrNY1Tz92SwLrCKbvHYrPUWjC8UAVaaX8tekdfRDF7DiIr8PX5me3YIZUBnQw0/loFNPhvQ==} - hasBin: true - - '@mcflyjs/config@0.2.1': - resolution: {integrity: sha512-u+4iKQtId3P8qvopl9p04gX2a/ahSmmrLx/uFp9bvg6UA6Y9tIhPG8l713QH7LVNr4iSIybukeuYbHxYHLiHYQ==} - '@mcflyjs/core@0.6.1': resolution: {integrity: sha512-ozgpIRpNix7gyuFlB8ofEm+iENMvxQsgyzxMTbIrOM69QYiEV1GsjxWyUqH3QuCwA4UB1KHdyWS+PNaCWSepOQ==} @@ -3139,22 +3132,6 @@ snapshots: - encoding - supports-color - '@mcflyjs/cli@0.1.1(magicast@0.3.5)': - dependencies: - c12: 2.0.1(magicast@0.3.5) - citty: 0.1.6 - consola: 3.3.3 - pathe: 1.1.2 - transitivePeerDependencies: - - magicast - - '@mcflyjs/config@0.2.1': - dependencies: - h3: 1.13.0 - web-component-base: 2.1.2 - transitivePeerDependencies: - - uWebSockets.js - '@mcflyjs/core@0.6.1(magicast@0.3.5)(typescript@5.7.2)': dependencies: c12: 2.0.1(magicast@0.3.5) diff --git a/site/mcfly.config.mjs b/site/mcfly.config.mjs index 1176ff1..07f3eef 100644 --- a/site/mcfly.config.mjs +++ b/site/mcfly.config.mjs @@ -5,6 +5,14 @@ export default defineMcFlyConfig({ server: { logs: true, }, + plugins: [ + { + 'mcfly:page:rendered': () => console.log('>>> page rendered'), + 'mcfly:scripts:evaluated': () => console.log('>>> scripts evaluated'), + 'mcfly:fragments:injected': () => console.log('>>> fragments injected'), + 'mcfly:elements:injected': () => console.log('>>> elements injected'), + }, + ], nitro: { devServer: { watch: ['../packages'], diff --git a/site/package.json b/site/package.json index bf51a20..d763dfc 100644 --- a/site/package.json +++ b/site/package.json @@ -13,8 +13,7 @@ "dependencies": { "@mcflyjs/cli": "workspace:*", "@mcflyjs/config": "workspace:*", - "@mcflyjs/core": "workspace:*", - "nitropack": "~2.10.4" + "@mcflyjs/core": "workspace:*" }, "version": "0.0.1", "main": "index.js",