From 3cc3d30bddb4d1a5b83e871d93a06245a96e36c6 Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Sat, 31 Aug 2024 00:13:45 +0200 Subject: [PATCH 1/7] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20add=20debug=20?= =?UTF-8?q?information=20to=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs/integration-with-app.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/docs/integration-with-app.md b/docs/docs/integration-with-app.md index c6ad91b0..a07b9d3b 100644 --- a/docs/docs/integration-with-app.md +++ b/docs/docs/integration-with-app.md @@ -296,6 +296,23 @@ Below you can see visualization of how the react component handles synchronizati Merkur - integration React - lifecycle methods +If you have some problems with integration you can turn on debug mode which can be helpful for solving your problems. + +```jsx +return ( + this._widgetMounted()} + onWidgetUnmouting={widget => this._widgetUnmounting()} + onError={error => this._handleError(error)}> +
+ Fallback for undefined widgetProperties +
+
+); +``` + ### Slots From 1cc1f80638c34f9fd9e06817b4bf0f07f00e5f5a Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Wed, 11 Sep 2024 22:10:25 +0200 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=F0=9F=90=9B=20playground=20template?= =?UTF-8?q?=20for=20defined=20containerSelector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/templates/body.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/templates/body.ejs b/packages/cli/src/templates/body.ejs index 8068a476..683cc73d 100644 --- a/packages/cli/src/templates/body.ejs +++ b/packages/cli/src/templates/body.ejs @@ -1,5 +1,5 @@ <% if(widgetProperties?.slot?.headline?.html){ %> -
+
<%- widgetProperties?.slot?.headline?.html %>
<% } %> @@ -9,7 +9,7 @@ __merkur__.create(<%- escapeToJSON(widgetProperties) %>) .then(function (widget) { widget.containerSelector = '.merkur-view'; - if (widget?.slot?.headline) { + if (widget?.slot?.headline && !widget?.slot?.headline?.containerSelector) { widget.slot.headline.containerSelector = '.headline-view'; } From f0835afb4917640f20c9ee8f96803eda473ef3d8 Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Wed, 11 Sep 2024 22:11:24 +0200 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=F0=9F=90=9B=20regular=20for=20test:?= =?UTF-8?q?all?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/tools/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/jest.config.js b/packages/tools/jest.config.js index 6f1ea8f2..2958a5b9 100644 --- a/packages/tools/jest.config.js +++ b/packages/tools/jest.config.js @@ -1,7 +1,7 @@ const testGroupRegexes = { unit: '(/__tests__/).*(Spec|test)\\.[jt]sx?$', integration: '(/__integration__/).*(Spec|test)\\.[jt]sx?$', - all: '(/__integration__/)|(/__tests__/).*(Spec|test)\\.[jt]sx?$', + all: '((/__integration__/)|(/__tests__/)).*(Spec|test)\\.[jt]sx?$', }; const testGroup = From 5e3497212a5dbbd62cda12b3bf034a3114a322f4 Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Wed, 11 Sep 2024 22:13:45 +0200 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20http.request=20retur?= =?UTF-8?q?ns=20error=20for=20rejected=20promise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugin-http-client/src/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/plugin-http-client/src/index.js b/packages/plugin-http-client/src/index.js index bd3f685a..592f61c7 100644 --- a/packages/plugin-http-client/src/index.js +++ b/packages/plugin-http-client/src/index.js @@ -73,7 +73,14 @@ function httpClientAPI() { ); if (!response.ok) { - return Promise.reject({ request, response }); + const error = new Error(`${response.statusText}: ${request.url}`, { + cause: { request, response }, + }); + // keep compatablity + error.request = request; + error.response = response; + + return Promise.reject(error); } return { request, response }; @@ -94,10 +101,10 @@ async function runTransformers(widget, transformers, method, ...rest) { function getFetchAPI() { if (typeof window === 'undefined') { - return global.fetch; + return global?.fetch; } - return window.fetch.bind(window); + return window?.fetch?.bind?.(window); } export function transformQuery() { From 5b2e49ac5d7010dd44721c915a58ce8bed52ce88 Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Fri, 13 Sep 2024 21:40:06 +0200 Subject: [PATCH 5/7] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20docum?= =?UTF-8?q?entation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_data/docs.json | 8 ++ docs/docs/getting-started.md | 2 +- docs/docs/merkur-cli.md | 83 +----------- docs/docs/merkur-config.md | 253 +++++++++++++++++++++++++++++++++++ docs/docs/router-plugin.md | 7 +- 5 files changed, 274 insertions(+), 79 deletions(-) create mode 100644 docs/docs/merkur-config.md diff --git a/docs/_data/docs.json b/docs/_data/docs.json index c3baa21c..85977b59 100644 --- a/docs/_data/docs.json +++ b/docs/_data/docs.json @@ -69,6 +69,14 @@ "title": "Merkur widget as custom element", "url": "/docs/register-custom-element" }, + { + "title": "CLI", + "url": "/docs/merkur-cli" + }, + { + "title": "Merkur config file", + "url": "/docs/merkur-config" + }, { "title": "Plugins", "url": "/docs/plugins" diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 97f22248..749aafee 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -72,7 +72,7 @@ The package.json contains predefined dependencies and defines some scripts, whic ### merkur.config.mjs -The configuration file for Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) +The [configuration file]({{ '/docs/merkur-config' | relative_url }}) for Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) ### src diff --git a/docs/docs/merkur-cli.md b/docs/docs/merkur-cli.md index dc023572..575cdf57 100644 --- a/docs/docs/merkur-cli.md +++ b/docs/docs/merkur-cli.md @@ -5,85 +5,16 @@ title: Merkur CLI # Merkur CLI -Merkur CLI for building your widget use [esbuild](https://esbuild.github.io/) tool which improve performance of development process and build tasks over [webpack](https://webpack.js.org/). The merkur CLi is configurable with `merkur.config.mjs` file which is in root of your project. - -The full merkur config can be look like: - -```javascript -/** - * @type import('@merkur/cli').defineConfig - */ -export default function ({ cliConfig, emitter, }) { - return { - extends: [ '@merkur/preact/cli' ], // Merkur predefined extender - task: { // defined tasks to building your widget, default are node, es13 and es9 - node: { - name: 'node', - build: ESBuildConfiguration, // https://esbuild.github.io/api/#build - } - es13: { - name: 'es13', - build: ESBuildConfiguration, // https://esbuild.github.io/api/#build - } - es9: { - name: 'es9', - build: ESBuildConfiguration, // https://esbuild.github.io/api/#build - } - }, - devServer: { // configuration for Merkur dev server - protocol: 'http:', - host: 'localhost:4445', - port: 4445, - staticPath: '/static', - staticFolder: '{project_folder}/build/static', - origin: 'http://localhost:4445' - }, - defaultEntries: { - // entries for your project, you can override it with creating /src/entries/{client|server.js} file - client: [ - '{project_folder}/node_modules/@merkur/preact/entries/client.js' - ], - server: [ - '{project_folder}/node_modules/@merkur/preact/entries/server.js' - ] - }, - playground: { - template: '{project_folder}/node_modules/@merkur/cli/src/templates/playground.ejs', - templateFolder: '{project_folder}/node_modules/@merkur/cli/src/templates', - path: '/', - widgetHandler: AsyncFunction, - widgetParams: Function, - }, - socketServer: { - protocol: 'ws:', - host: 'localhost:4321', - port: 4321 - }, - widgetServer: { // configuration for Merkur widget production server - protocol: 'http:', - host: 'localhost:4444', - port: 4444, - staticPath: '/static', - staticFolder: '{project_folder}/build/static', - buildFolder: '{project_folder}/build', - clusters: 3, - origin: 'http://localhost:4444' - }, - HMR: true, - constant: { - HOST: 'localhost', - } - onCliConfig, Function, //extending hook for cli config, - onMerkurConfig: Function, // extending hook for merkur config, - onTaskConfig: Function, // extending hook for task config, - onTaskBuild, Function, // extending hook esbuild config - }; -} -``` +Merkur CLI for building your widget use [esbuild](https://esbuild.github.io/) tool which improve performance of development process and build tasks over [webpack](https://webpack.js.org/). The merkur CLi is configurable with [merkur.config.mjs]({{ '/docs/merkur-config' | relative_url }}) file which is in root of your project. ## Commands - `merkur dev` - build your widget with with NODE_ENV = 'development' and with watch mode - `merkur build` - build your widget with NODE_ENV = 'production' - `merkur test` - run defined widget tests with NODE_ENV = 'test' -- `merkur start` - run widget server and playground server \ No newline at end of file +- `merkur start` - run widget server and playground server +- `merkur custom` - customize part of template (playground page) + +## Custom playground template + +At first run `merkur custom playground:body` command which create `body.ejs` file in your project in `/server/playground/templates` folder. Now you can modify playground page as you wish. diff --git a/docs/docs/merkur-config.md b/docs/docs/merkur-config.md new file mode 100644 index 00000000..2228f589 --- /dev/null +++ b/docs/docs/merkur-config.md @@ -0,0 +1,253 @@ +--- +layout: docs +title: Merkur config +--- + +# Merkur config + +The `merkur.config.mjs` file is main configuration file for Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) and your project. + +The full merkur config can be look like: + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + return { + extends: [ '@merkur/preact/cli' ], // Merkur predefined extender + task: { // defined tasks to building your widget, default are node, es13 and es9 + node: { + name: 'node', + build: ESBuildConfiguration, // https://esbuild.github.io/api/#build + } + es13: { + name: 'es13', + folder: 'es13', + build: ESBuildConfiguration, // https://esbuild.github.io/api/#build + } + es9: { + name: 'es9', + folder: 'es9', + build: ESBuildConfiguration, // https://esbuild.github.io/api/#build + } + }, + devServer: { // configuration for Merkur dev server + protocol: 'http:', + host: 'localhost:4445', + port: 4445, + staticPath: '/static', + staticFolder: '{project_folder}/build/static', + origin: 'http://localhost:4445' + }, + defaultEntries: { + // entries for your project, you can override it with creating /src/entries/{client|server.js} file + client: [ + '{project_folder}/node_modules/@merkur/preact/entries/client.js' + ], + server: [ + '{project_folder}/node_modules/@merkur/preact/entries/server.js' + ] + }, + playground: { + template: '{project_folder}/node_modules/@merkur/cli/src/templates/playground.ejs', + templateFolder: '{project_folder}/node_modules/@merkur/cli/src/templates', + serverTemplateFolder: '{project_folder}/server/playground/templates', + path: '/', + widgetHandler: AsyncFunction, + widgetParams: Function, + }, + socketServer: { + protocol: 'ws:', + host: 'localhost:4321', + port: 4321 + }, + widgetServer: { // configuration for Merkur widget production server + protocol: 'http:', + host: 'localhost:4444', + port: 4444, + staticPath: '/static', + staticFolder: '{project_folder}/build/static', + buildFolder: '{project_folder}/build', + clusters: 3, + origin: 'http://localhost:4444' + }, + HMR: true, + constant: { + HOST: 'localhost', + } + onCliConfig, Function, //extending hook for cli config, + onMerkurConfig: Function, // extending hook for merkur config, + onTaskConfig: Function, // extending hook for task config, + onTaskBuild, Function, // extending hook esbuild config + }; +} +``` + +## How to define custom task + +For example we create custom task for bundling ES11 version of Merkur widget very simple. + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + return { + task: { + es11: { + name: 'es11', + folder: 'es11', + build: { // ESBuildConfiguration option + platform: 'browser', + target: 'es2020', + outdir: `${cliConfig.staticFolder}/es11` + } + } + }, + }; +} +``` + +Or If you want to bundle Merkur widget and some JS asset for your Merkur widget with own entry point. You can use Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) to define custom tasks for that asset. For example: + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + return { + task: { + es13Asset: { + name: 'es13Asset', + folder: 'es13', + build: { + entryPoints: `${cliConfig.projectFolder}/src/customAsset.js`, + entryNames: !cliConfig.isProduction ? 'customAsset' : 'customAsset.[hash]', + platform: 'browser', + outdir: `${staticFolder}/es13`, + plugins: merkurConfig.task['es13'].build.plugins, + }, + }, + es9Asset: { + name: 'es9Asset', + folder: 'es9', + build: { + entryPoints: `${cliConfig.projectFolder}/src/customAsset.js`, + entryNames: !cliConfig.isProduction ? 'customAsset' : 'customAsset.[hash]', + platform: 'browser', + target: 'es2018', + outdir: `${staticFolder}/es9`, + plugins: merkurConfig.task['es9'].build.plugins, + }, + } + }, + onCliConfig({ cliConfig }) { + // add es13Asset task to be run for `merkur dev` + if (cliConfig.command === 'dev') { + cliConfig.runTask.push('es13Asset'); + } + }, + }; +} +``` + +## How to add esbuild plugin + +By default Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) as esbuild load only css files. If you want to use some css preprocessors like `less` or others. You must install esbuild plugin for that with `npm install esbuild-plugin-less --save dev` command. Then add new installed package in `merkur.config.js` to [esbuild](https://esbuild.github.io/) configuration. + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + const loaders = []; + + try { + const { lessLoader } = await import('esbuild-plugin-less'); + + loaders.push( + lessLoader({}) + ); + } catch { + // Fail silently + } + + return { + onTaskBuild({ build }) { + build.plugins.push(...loaders); + + return build; + }, + }; +} +``` +The `esbuild-plugin-*` must be dynamic imported with `try/catch` block because `merkur.config.js` is used for all Merkur [CLI]({{ '/docs/merkur-cli' | relative_url }}) commands and of course for `merkur start` where dev dependencies can be missed. It depends on your CI/CD workflow. But we predict that you run `merkur start` command only with production dependencies where dev dependencies miss. + +Or you want to use [Tailwind CSS](https://tailwindui.com/) framework. You must install esbuild plugin with `npm install esbuild-plugin-tailwindcss --save dev` command. Then add new installed package in `merkur.config.js` to [esbuild](https://esbuild.github.io/) configuration. + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + const loaders = []; + + try { + const { tailwindPlugin } = await import('esbuild-plugin-tailwindcss'); + + loaders.push( + tailwindPlugin({}) + ); + } catch { + // Fail silently + } + + return { + onTaskBuild({ build }) { + build.plugins.push(...loaders); + + return build; + }, + }; +} +``` + +You must create file `tailwind.config.js` at the root of the project. + +```javascript +// ./tailwind.config.js +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + // For more, see: https://tailwindcss.com/docs/configuration +}; +``` + +You must create file index.css and import index.css file to `./src/widget.js`. + +```css + /* ./src/style.css */ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; +``` + +## How to override playground widgetParams to widget API + +By default playground page pass all route query params to widgetParams. If you want to modify that logic which can be helpful for example with `@merkur/plugin-router` which route defined routes by `pathname` then is useful set widgetParams `pathname` to `req.path`. You can also reconfigure regular `path` for playground page for which playground page works. + +```javascript +/** + * @type import('@merkur/cli').defineConfig + */ +export default function ({ cliConfig, emitter, }) { + return { + playground: { + widgetParams: req => { + return new URLSearchParams({ pathname: req.path }); + }, + path: /(\/$|\/some-folder\/.*)/g + } + } +} +``` \ No newline at end of file diff --git a/docs/docs/router-plugin.md b/docs/docs/router-plugin.md index a79a4418..95276124 100644 --- a/docs/docs/router-plugin.md +++ b/docs/docs/router-plugin.md @@ -5,7 +5,7 @@ title: Router plugin - Merkur # Router plugin -The Merkur router plugin is integration of [universal-router](https://www.npmjs.com/package/universal-router) to Merkur ecosystem and extend `@merkur/plugin-component` for activation only part of widget functionality (controller) for defined path. The plugin adds `router` property to your widget with a `link`, `redirect` and `getCurrentRoute` methods. +The Merkur router plugin is integration of [universal-router](https://www.npmjs.com/package/universal-router) to Merkur ecosystem and extend `@merkur/plugin-component` for activation only part of widget functionality (controller) for defined path. The plugin adds `router` property to your widget with a `link`, `redirect`, `getCurrentRoute` and `getCurrentContext`(context from [universal-router](https://www.npmjs.com/package/universal-router)) methods. ## Installation @@ -64,7 +64,7 @@ export default defineWidget({ ``` -We have a `router.{link|redirect|getCurrentRoute}` methods available on the widget now. +We have a `router.{link|redirect|getCurrentRoute|getCurrentContext}` methods available on the widget now. After that we must initialize universal router with own routes and options in setup phase of creation widget where structure for `routes` and `options` are defined from [universal-router](https://github.com/kriasoft/universal-router/blob/main/docs/api.md). The `options` are extended by Merkur with optional settings `protocol` and `host` for generating absolute url address from `link` method. Returns type from `route.action` method is defined by Merkur router plugin. It is a object with `PageView` as main rendered component for defined path and controller life cycle methods **(init, load, activate, deactivate, destroy)** which extend `load`, `mount`, `unmount` methods from `@merkur/plugin-component` and other controller custom methods with logic for defined path. The `mount` method use under the hood `widget.viewFactory` method to resolving component for render. So we must set View in createViewFactory as route PageView. If you don't have slots you can set `slotFactories` as empty array or set as route slots. @@ -211,6 +211,9 @@ Now you have install router plugin to your widget. ### getCurrentRoute Returned object from `route.action` method for current route. +### getCurrentContext +Returned context(first argument) from `route.action` method for current route. + ### link - name - route defined name - data - route arguments From a265e5d93c1cbf35804ffa6d52b58d793221ea6a Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Fri, 13 Sep 2024 21:41:00 +0200 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20new=20getCurre?= =?UTF-8?q?ntContext=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin-router/src/__tests__/indexSpec.js | 2 + packages/plugin-router/src/index.js | 60 ++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/packages/plugin-router/src/__tests__/indexSpec.js b/packages/plugin-router/src/__tests__/indexSpec.js index 9be285dd..e27ce449 100644 --- a/packages/plugin-router/src/__tests__/indexSpec.js +++ b/packages/plugin-router/src/__tests__/indexSpec.js @@ -44,6 +44,7 @@ describe('createWidget method with router plugin', () => { "event": {}, }, "router": { + "context": null, "isBootstrapCalled": false, "isMounting": false, "isRouteActivated": false, @@ -86,6 +87,7 @@ describe('createWidget method with router plugin', () => { "pathname": "/my-route", }, "router": { + "getCurrentContext": [Function], "getCurrentRoute": [Function], "link": [Function], "redirect": [Function], diff --git a/packages/plugin-router/src/index.js b/packages/plugin-router/src/index.js index 67d0312c..32552b1b 100644 --- a/packages/plugin-router/src/index.js +++ b/packages/plugin-router/src/index.js @@ -21,7 +21,27 @@ const ENV = : DEV; export function createRouter(widget, routes, options) { - widget.$dependencies.router = new UniversalRouter(routes, options); + let wrappedRoutes = routes.reduce((result, route) => { + const clonedRoute = { ...route }; + + if (isFunction(clonedRoute.action)) { + const originAction = clonedRoute.action; + clonedRoute.action = (...rest) => { + // @TODO UniversalRouter don't parse query from url so context.params are only named route parameters + // For some application can be helpful to have named route parameters and query parameters merged + // because link method support both named parameters and query parameters + widget.$in.router.context = rest[0]; + + return originAction(...rest); + }; + } + + result.push(clonedRoute); + + return result; + }, []); + + widget.$dependencies.router = new UniversalRouter(wrappedRoutes, options); widget.$dependencies.link = generateUrls(widget.$dependencies.router, { stringifyQueryParams: (params) => new URLSearchParams(params).toString(), }); @@ -35,6 +55,7 @@ export function routerPlugin() { widget.$in.router = { route: null, + context: null, options: {}, pathname: null, isMounting: false, @@ -110,6 +131,9 @@ function routerAPI() { getCurrentRoute(widget) { return widget.$in.router.route; }, + getCurrentContext(widget) { + return widget.$in.router.context; + }, }, }; } @@ -143,6 +167,7 @@ async function loadHook(widget, originalLoad, ...rest) { : Promise.resolve({}); const routeStatePromise = plugin.route.load(widget, { route: plugin.route, + context: plugin.context, args: rest, globalState: globalStatePromise, }); @@ -168,7 +193,11 @@ async function mountHook(widget, originalMount, ...rest) { const result = await originalMount(...rest); if (plugin.isMounting && isFunction(plugin.route.init)) { - await plugin.route.init(widget, { route: plugin.route, args: rest }); + await plugin.route.init(widget, { + route: plugin.route, + context: plugin.context, + args: rest, + }); } if ( @@ -177,7 +206,11 @@ async function mountHook(widget, originalMount, ...rest) { !plugin.isRouteActivated ) { plugin.isRouteActivated = true; - plugin.route.activate(widget, { route: plugin.route, args: rest }); + plugin.route.activate(widget, { + route: plugin.route, + context: plugin.context, + args: rest, + }); } plugin.isMounting = false; @@ -197,7 +230,11 @@ async function updateHook(widget, originalUpdate, ...rest) { !plugin.isRouteActivated ) { plugin.isRouteActivated = true; - plugin.route.activate(widget, { route: plugin.route, args: rest }); + plugin.route.activate(widget, { + route: plugin.route, + context: plugin.context, + args: rest, + }); } return result; @@ -238,9 +275,10 @@ async function resolveRoute(widget) { async function setupRouterCycle(widget, ...rest) { const route = await resolveRoute(widget); + const plugin = widget.$in.router; if (isFunction(route.init)) { - await route.init(widget, { route, args: rest }); + await route.init(widget, { route, context: plugin.context, args: rest }); } } @@ -251,11 +289,19 @@ async function tearDownRouterCycle(widget, ...rest) { if (route) { if (isFunction(route.deactivate) && isRouteActivated === true) { - await route.deactivate(widget, { route, args: rest }); + await route.deactivate(widget, { + route, + context: plugin.context, + args: rest, + }); } if (isFunction(route.destroy)) { - await route.destroy(widget, { route, args: rest }); + await route.destroy(widget, { + route, + context: plugin.context, + args: rest, + }); } } From 5964dcb99ec3e5dae76019e64bacc2fdffffb1a9 Mon Sep 17 00:00:00 2001 From: Miroslav Jancarik Date: Fri, 13 Sep 2024 21:42:23 +0200 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20new=20custom?= =?UTF-8?q?=20command=20for=20extendability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/bin/merkur.mjs | 21 +++++++++++- packages/cli/src/commands/constant.mjs | 1 + packages/cli/src/commands/custom.mjs | 47 ++++++++++++++++++++++++++ packages/cli/src/devServer.mjs | 5 +-- packages/cli/src/merkurConfig.mjs | 3 ++ packages/cli/types.d.ts | 2 ++ 6 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/commands/custom.mjs diff --git a/packages/cli/bin/merkur.mjs b/packages/cli/bin/merkur.mjs index bb2db2f6..056dcf3a 100755 --- a/packages/cli/bin/merkur.mjs +++ b/packages/cli/bin/merkur.mjs @@ -1,9 +1,10 @@ #!/usr/bin/env node -import { Command, Option } from 'commander'; +import { Command, Option, Argument } from 'commander'; import { dev } from '../src/commands/dev.mjs'; import { build } from '../src/commands/build.mjs'; import { start } from '../src/commands/start.mjs'; import { test } from '../src/commands/test.mjs'; +import { custom, CUSTOM_PART } from '../src/commands/custom.mjs'; import { COMMAND_NAME } from '../src/commands/constant.mjs'; // eslint-disable-next-line @@ -66,6 +67,7 @@ program.command(COMMAND_NAME.DEV) program .command(COMMAND_NAME.BUILD) + .description('Build command') .addOption(writeToDiskOption) .addOption(sourcemapOption) .addOption(runTasksOption) @@ -85,6 +87,7 @@ program program .command(COMMAND_NAME.START) + .description('Start widget server') .addOption(portOption) .addOption(devServerPortOption) .addOption(projectFolderOption) @@ -106,6 +109,7 @@ program program .command(COMMAND_NAME.TEST) + .description('Test widget') .allowUnknownOption() .action(async (options, cmd) => { process.env.NODE_ENV = process.env.NODE_ENV ?? 'test'; @@ -117,4 +121,19 @@ program await test({ args, commandArgs: cmd.args, command: COMMAND_NAME.TEST }); }); +program + .command(COMMAND_NAME.CUSTOM) + .description('Customize template') + .addArgument(new Argument('', 'custom part').choices(Object.values(CUSTOM_PART))) + .addOption(verboseOption) + .allowUnknownOption() + .action(async (part, options, cmd) => { + const args = { + ...options, + part, + }; + + await custom({ args, commandArgs: cmd.args, command: COMMAND_NAME.CUSTOM }); + }); + program.parse(process.argv); diff --git a/packages/cli/src/commands/constant.mjs b/packages/cli/src/commands/constant.mjs index 7ddb1e6b..7cc6d45e 100644 --- a/packages/cli/src/commands/constant.mjs +++ b/packages/cli/src/commands/constant.mjs @@ -3,4 +3,5 @@ export const COMMAND_NAME = { DEV: 'dev', BUILD: 'build', TEST: 'test', + CUSTOM: 'custom', }; diff --git a/packages/cli/src/commands/custom.mjs b/packages/cli/src/commands/custom.mjs new file mode 100644 index 00000000..0a673143 --- /dev/null +++ b/packages/cli/src/commands/custom.mjs @@ -0,0 +1,47 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import chalk from 'chalk'; + +import { createCLIConfig } from '../CLIConfig.mjs'; +import { createContext } from '../context.mjs'; +import { createLogger } from '../logger.mjs'; +import { createMerkurConfig } from '../merkurConfig.mjs'; +import { handleExit } from '../handleExit.mjs'; + +export const CUSTOM_PART = { + PLAYGROUND_BODY: 'playground:body', +}; + +export async function custom({ args, command }) { + const context = await createContext(); + let baseCliConfig = await createCLIConfig({ args, command }); + + const { merkurConfig, cliConfig } = await createMerkurConfig({ + args, + cliConfig: baseCliConfig, + context, + }); + + const logger = createLogger('Custom command:', cliConfig); + + await handleExit({ context }); + switch (args.part) { + case CUSTOM_PART.PLAYGROUND_BODY: { + const file = 'body.ejs'; + const src = path.resolve( + `${merkurConfig.playground.templateFolder}/${file}`, + ); + const dest = path.resolve( + `${merkurConfig.playground.serverTemplateFolder}/${file}`, + ); + + logger.debug(`Copy file from ${src} to ${dest}`); + + await fs.copyFile(src, dest); + + logger.info(`You can customize ${chalk.green(dest)} file.`); + break; + } + } +} diff --git a/packages/cli/src/devServer.mjs b/packages/cli/src/devServer.mjs index bbc08a70..7ee29cfd 100644 --- a/packages/cli/src/devServer.mjs +++ b/packages/cli/src/devServer.mjs @@ -26,10 +26,11 @@ export async function runDevServer({ context, merkurConfig, cliConfig }) { const { template, templateFolder, + serverTemplateFolder, path: playgroundPath, widgetHandler, } = merkurConfig.playground; - const { cliFolder, projectFolder, command, writeToDisk } = cliConfig; + const { cliFolder, command, writeToDisk } = cliConfig; return new Promise((resolve, reject) => { const app = express(); @@ -126,7 +127,7 @@ export async function runDevServer({ context, merkurConfig, cliConfig }) { fs.readFileSync(template, 'utf8'), { views: [ - path.resolve(`${projectFolder}/server/playground/templates/`), + serverTemplateFolder, path.dirname(template), templateFolder, ], diff --git a/packages/cli/src/merkurConfig.mjs b/packages/cli/src/merkurConfig.mjs index 5e53d558..23951203 100644 --- a/packages/cli/src/merkurConfig.mjs +++ b/packages/cli/src/merkurConfig.mjs @@ -194,6 +194,9 @@ emitter.on( merkurConfig.playground = { template: path.resolve(`${cliConfig.cliFolder}/templates/playground.ejs`), templateFolder: path.resolve(`${cliConfig.cliFolder}/templates`), + serverTemplateFolder: path.resolve( + `${cliConfig.projectFolder}/server/playground/templates`, + ), path: '/', widgetHandler: async (req) => { const { protocol, host } = merkurConfig.widgetServer; diff --git a/packages/cli/types.d.ts b/packages/cli/types.d.ts index d99abfc9..9eda957e 100644 --- a/packages/cli/types.d.ts +++ b/packages/cli/types.d.ts @@ -30,6 +30,7 @@ export type Extend = export interface Task { name: 'es13' | 'es9' | 'node' | string; + folder: 'es13' | 'es9' | string; build: BuildOptions; [key: string]: any; } @@ -46,6 +47,7 @@ export interface DevServer { export interface Playground { template: string; templateFolder: string; + serverTemplateFolder: string; path: string; widgetHandler(req: Request, res: Response): Record; widgetParams(req: Request): URLSearchParams;