From 66396fac0df4e2a7cb1f6273d1aa819714637a0b Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Wed, 30 Mar 2022 18:28:17 +0800 Subject: [PATCH 1/6] First draft --- docs/_markbind/layouts/devGuide.md | 1 + docs/devGuide/writingComponents.md | 115 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 docs/devGuide/writingComponents.md diff --git a/docs/_markbind/layouts/devGuide.md b/docs/_markbind/layouts/devGuide.md index 6412945cdf..33c9dc6abd 100644 --- a/docs/_markbind/layouts/devGuide.md +++ b/docs/_markbind/layouts/devGuide.md @@ -14,6 +14,7 @@ * [Project Structure]({{baseUrl}}/devGuide/design/projectStructure.html) * [Architecture]({{baseUrl}}/devGuide/design/architecture.html) * [Server Side Rendering]({{baseUrl}}/devGuide/design/serverSideRendering.html) +* [Writing Components]({{baseUrl}}/devGuide/writingComponents.html) * [Writing Plugins]({{baseUrl}}/devGuide/writingPlugins.html) * [Project management]({{baseUrl}}/devGuide/projectManagement.html) * Appendices :expanded: diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md new file mode 100644 index 0000000000..5edc9f2915 --- /dev/null +++ b/docs/devGuide/writingComponents.md @@ -0,0 +1,115 @@ +{% set title = "Writing Components" %} +{{ title }} + + + title: "{{ title }}" + layout: devGuide.md + pageNav: default + + +# {{ title }} + +
+ +This page explains various concerns related to MarkBind components, focused on implementation and testing. +
+ +## Implementing Components + +There are multiple ways to implement MarkBind components. + + + +If an author has a conflicting slot and attribute, MarkBind should **log a warning** to let them know! + + +### Transforming the Node Directly + +One way to implement a MarkBind component is to transform the node itself. +This is a more low-level implementation that can be useful when a node only needs to be modified slightly. + +When a node is processed, MarkBind syntax is converted to HTML, and any remaining attributes will also be converted to HTML attributes. +This can be useful if you just need to add a HTML attribute to the node, or modify the value of an existing attribute. + +These transformations may take place at various stages of node processing: before (`preProcessNode`), during (`processNode`), or after (`postProcessNode`). + +{{ icon_examples }} +* Adding a class to a node (setting line numbers for code blocks) +* Modifying attributes and adding a directive to a node ([former implementation](https://github.com/MarkBind/markbind/blob/502df135e07baebd9d4eea8ccc0654c990047792/packages/core/src/html/bootstrapVueProcessor.js#L73) of popovers) + + + +### Vue Components + +Many MarkBind components are implemented as Vue components, either by creating a component in the `vue-components` package, or by importing a component from an external library. +This can be useful when a more complicated set of features is needed, where a Vue component can provide an interface for us to manage these functionalities. + +Vue components are registered in `vue-components/src/index.js`, which allows them to be used in any Vue instance without needing to be imported first. + + + +##### Attributes + +MarkBind attributes are passed to the Vue component as **props**. The type of the prop will be a `String`. + +##### Slots + +MarkBind slots are passed as **named slots** to the Vue component. The name of the MarkBind slot will be the same as the name of the Vue slot. +Hence, MarkBind slots can be accessed in a Vue component either through the [named slots](https://v2.vuejs.org/v2/guide/components-slots.html#Named-Slots) or through the [`$slots` API](https://v2.vuejs.org/v2/api/#vm-slots). + + +
+ +{{ icon_examples }} +* As a wrapper for an external library (Modal component) +* To implement a set of customised behaviours (Quiz component) + +### As a Plugin + +MarkBind components can be implemented as a plugin as well. +This is suitable for more lightweight components where the implementation is largely in processing the node, making it suitable to use MarkBind Plugins' `processNode` or `postRender` interfaces. + +{{ icon_examples }} +* The `tree` component is implemented as a default plugin + +## Testing Components + +Automated tests that are relevant to the components include: + +* [Functional tests]({{baseUrl}}/devGuide/workflow.html#adding-test-site-content) +* [Snapshot tests]({{baseUrl}}/devGuide/workflow.html#adding-snapshot-tests-for-components) + +The API for Snapshot tests can be found at [Vue Test Utils](https://v1.test-utils.vuejs.org/). + +Additionally, it's a good idea to check the deployed PR preview in addition to serving the app locally, to ensure that there are no differences. + +## Additional Considerations + +Some things you may need to consider when implementing a MarkBind component: + +#### Reactivity + +_Reactivity_ refers to the ability of a web framework to update your view whenever the application state has changed. +It is important to consider reactivity when implementing a component that may have dynamic contents that readers can interact with (e.g. opening a panel, triggering a tooltip to show). + +#### SSR + +Components should be compatible with SSR. +Minimally, there should be no SSR issues (viewable from the browser console), though a lack of warnings does **not** mean that there are no SSR problems. +A guide on SSR for MarkBind can be found [here]({{baseUrl}}/devGuide/serverSideRendering.html). + +Vue-component-specific tips for resolving SSR issues: +* The `mount` and `beforeMount` lifecycle hooks will only be executed on the client, not the server +* When using `v-if`, ensure that it will evaluate to the same value on both the client and server +* Take note of how the Vue component will be compiled, ensure that the HTML is correct and aligns on both client- and server- side +* Conditionally render data when it has been fully loaded + +#### Bundle size + +When creating a new component, you may need to import a package or library to support some functionality. +Ideally, this should not increase MarkBind's bundle size too much. + +#### Dependencies + +When choosing to use an external library, it should ideally be well-maintained and not have too many dependencies, especially high-level dependencies. High-level dependencies may lag behind the most recent releases of other libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. + From 7508094d85111a0f430efdae0f2aba312dfe4ac4 Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Wed, 30 Mar 2022 21:43:09 +0800 Subject: [PATCH 2/6] Minor wording edits --- docs/devGuide/writingComponents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md index 5edc9f2915..11516abc36 100644 --- a/docs/devGuide/writingComponents.md +++ b/docs/devGuide/writingComponents.md @@ -44,7 +44,7 @@ These transformations may take place at various stages of node processing: befor Many MarkBind components are implemented as Vue components, either by creating a component in the `vue-components` package, or by importing a component from an external library. This can be useful when a more complicated set of features is needed, where a Vue component can provide an interface for us to manage these functionalities. -Vue components are registered in `vue-components/src/index.js`, which allows them to be used in any Vue instance without needing to be imported first. +Vue components are registered in `vue-components/src/index.js`, which allows them to be used in the template section of any Vue instance without needing to be imported first. @@ -67,7 +67,7 @@ Hence, MarkBind slots can be accessed in a Vue component either through the [nam ### As a Plugin MarkBind components can be implemented as a plugin as well. -This is suitable for more lightweight components where the implementation is largely in processing the node, making it suitable to use MarkBind Plugins' `processNode` or `postRender` interfaces. +This is suitable for more lightweight components where the implementation is largely in processing the node, where it is fitting to use MarkBind Plugins' `processNode` or `postRender` interfaces. {{ icon_examples }} * The `tree` component is implemented as a default plugin From c57108a754a95231154331c0bc2b7269548db0dd Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Thu, 31 Mar 2022 16:08:46 +0800 Subject: [PATCH 3/6] Add node processing flow --- docs/devGuide/writingComponents.md | 47 +++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md index 11516abc36..78f20d9588 100644 --- a/docs/devGuide/writingComponents.md +++ b/docs/devGuide/writingComponents.md @@ -14,14 +14,30 @@ This page explains various concerns related to MarkBind components, focused on implementation and testing. -## Implementing Components +MarkBind provides a number of components (e.g. expandable panels, tooltips) dynamically express content. +In order to serve content on the browser, MarkBind syntax is converted to valid HTML. -There are multiple ways to implement MarkBind components. + - +The main logic of the node processing flow can be found in `NodeProcessor`. -If an author has a conflicting slot and attribute, MarkBind should **log a warning** to let them know! - +A MarkBind source file is first parsed into a series of +nodes. +In general, each component will be parsed as a node, which may contain other childnodes (components or otherwise). + +Each node is then processed to implement MarkBind functionalities, such as checking for invalid intrasite links and rendering markdown. +Components may undergo further processing (in `processNode`) and/or post-processing (in `postProcessNode`) to further transform the node to the desired HTML. +MarkBind identifies each component by the node's _name_ (e.g. `panel`, `question`). + +`cheerio` is then used to convert all nodes back into HTML, and this HTML is served to the browser as a MarkBind page. + + +
+
+ +## Implementing Components + +There are multiple ways to implement MarkBind components. ### Transforming the Node Directly @@ -96,7 +112,7 @@ It is important to consider reactivity when implementing a component that may ha Components should be compatible with SSR. Minimally, there should be no SSR issues (viewable from the browser console), though a lack of warnings does **not** mean that there are no SSR problems. -A guide on SSR for MarkBind can be found [here]({{baseUrl}}/devGuide/serverSideRendering.html). +A guide on SSR for MarkBind can be found [here]({{baseUrl}}/devGuide/design/serverSideRendering.html). Vue-component-specific tips for resolving SSR issues: * The `mount` and `beforeMount` lifecycle hooks will only be executed on the client, not the server @@ -111,5 +127,22 @@ Ideally, this should not increase MarkBind's bundle size too much. #### Dependencies -When choosing to use an external library, it should ideally be well-maintained and not have too many dependencies, especially high-level dependencies. High-level dependencies may lag behind the most recent releases of other libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. +When choosing to use a third-party library or package, it should ideally be well-maintained and not have too many dependencies, especially high-level dependencies. +While dependencies may be inevitable, a package that depends on higher-level libraries may lag behind the most recent releases of these libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. + + + +You are welcome to raise any concerns during the initial discussion phase for other devs to weigh in on the tradeoffs! + + +#### Attributes and Slots + +MarkBind components may support attributes, slots or both. +Some components allow users to supply the same content as either a slot or an attribute. +If an author provides the same content as both a slot and an attribute, in most cases, the slot should override the attribute. + + + +MarkBind should also **log a warning** to inform the author of this conflict! + From fa4fa089309e3eaf927d13b88e4b0b6215b1d4ff Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Thu, 31 Mar 2022 16:10:51 +0800 Subject: [PATCH 4/6] Edit wording --- docs/devGuide/writingComponents.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md index 78f20d9588..2fe71b7c3b 100644 --- a/docs/devGuide/writingComponents.md +++ b/docs/devGuide/writingComponents.md @@ -11,13 +11,13 @@
-This page explains various concerns related to MarkBind components, focused on implementation and testing. +This page explains how MarkBind components work, focused on implementation and testing.
MarkBind provides a number of components (e.g. expandable panels, tooltips) dynamically express content. In order to serve content on the browser, MarkBind syntax is converted to valid HTML. - + The main logic of the node processing flow can be found in `NodeProcessor`. From 2672023cc38b274b73d3ff7327212c62c6248da5 Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Fri, 1 Apr 2022 03:12:51 +0800 Subject: [PATCH 5/6] More fixes --- docs/devGuide/writingComponents.md | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md index 2fe71b7c3b..236d2e3c1a 100644 --- a/docs/devGuide/writingComponents.md +++ b/docs/devGuide/writingComponents.md @@ -14,12 +14,12 @@ This page explains how MarkBind components work, focused on implementation and testing. -MarkBind provides a number of components (e.g. expandable panels, tooltips) dynamically express content. +MarkBind provides a number of components (e.g. expandable panels, tooltips) to dynamically express content. In order to serve content on the browser, MarkBind syntax is converted to valid HTML. -The main logic of the node processing flow can be found in `NodeProcessor`. +The main logic of the node processing flow can be found in [`packages/core/src/html/NodeProcessor.js`](https://github.com/MarkBind/markbind/blob/master/packages/core/src/html/NodeProcessor.js). A MarkBind source file is first parsed into a series of nodes. @@ -64,11 +64,11 @@ Vue components are registered in `vue-components/src/index.js`, which allows the -##### Attributes +##### Attributes MarkBind attributes are passed to the Vue component as **props**. The type of the prop will be a `String`. -##### Slots +##### Slots MarkBind slots are passed as **named slots** to the Vue component. The name of the MarkBind slot will be the same as the name of the Vue slot. Hence, MarkBind slots can be accessed in a Vue component either through the [named slots](https://v2.vuejs.org/v2/guide/components-slots.html#Named-Slots) or through the [`$slots` API](https://v2.vuejs.org/v2/api/#vm-slots). @@ -77,16 +77,19 @@ Hence, MarkBind slots can be accessed in a Vue component either through the [nam
{{ icon_examples }} -* As a wrapper for an external library (Modal component) -* To implement a set of customised behaviours (Quiz component) +* As a wrapper for an external library ([Modal component](https://github.com/MarkBind/markbind/blob/master/packages/vue-components/src/Modal.vue)) +* To implement a set of customised behaviours ([Quiz component](https://github.com/MarkBind/markbind/blob/master/packages/vue-components/src/questions/Quiz.vue)) ### As a Plugin MarkBind components can be implemented as a plugin as well. -This is suitable for more lightweight components where the implementation is largely in processing the node, where it is fitting to use MarkBind Plugins' `processNode` or `postRender` interfaces. +This is suitable for more lightweight components where the implementation is largely in processing the node, where it is fitting to use MarkBind plugins' `processNode` or `postRender` interfaces. +These interfaces provide additional entry points for modifying the page generated, and do not replace MarkBind's usual node processing. + +The [Writing Plugins]({{baseUrl}}/devGuide/writingPlugins.html) guide is a good place to get started on plugins. {{ icon_examples }} -* The `tree` component is implemented as a default plugin +* The [`tree` component](https://github.com/MarkBind/markbind/blob/master/packages/core/src/plugins/default/markbind-plugin-tree.js) is implemented as a default plugin ## Testing Components @@ -110,7 +113,7 @@ It is important to consider reactivity when implementing a component that may ha #### SSR -Components should be compatible with SSR. +Components should be compatible with SSR (Server-Side Rendering). Minimally, there should be no SSR issues (viewable from the browser console), though a lack of warnings does **not** mean that there are no SSR problems. A guide on SSR for MarkBind can be found [here]({{baseUrl}}/devGuide/design/serverSideRendering.html). @@ -124,24 +127,27 @@ Vue-component-specific tips for resolving SSR issues: When creating a new component, you may need to import a package or library to support some functionality. Ideally, this should not increase MarkBind's bundle size too much. +[Bundlephobia](https://bundlephobia.com/) may be useful to quickly look up the size of a package! #### Dependencies -When choosing to use a third-party library or package, it should ideally be well-maintained and not have too many dependencies, especially high-level dependencies. -While dependencies may be inevitable, a package that depends on higher-level libraries may lag behind the most recent releases of these libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. +When choosing to use a third-party library or package, it should ideally be well-maintained and not have too many dependencies. +While dependencies may be inevitable, a package with dependencies on large libraries may lag behind the most recent releases of these libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. + +For instance, if `bootstrap-vue` depends on Bootstrap and Vue, MarkBind will need to wait for `bootstrap-vue` to migrate to the newest versions of both Bootstrap and Vue before MarkBind can migrate to these versions of Bootstrap and Vue as well. - + -You are welcome to raise any concerns during the initial discussion phase for other devs to weigh in on the tradeoffs! +Feel free to raise any concerns during the initial discussion phase for other devs to weigh in on the tradeoffs! #### Attributes and Slots -MarkBind components may support attributes, slots or both. +MarkBind components may support attributes, slots or both. Some components allow users to supply the same content as either a slot or an attribute. If an author provides the same content as both a slot and an attribute, in most cases, the slot should override the attribute. - + MarkBind should also **log a warning** to inform the author of this conflict! From 7303f85eb5f597a9694059bad277ababc1394348 Mon Sep 17 00:00:00 2001 From: Jovyn Tan Date: Fri, 1 Apr 2022 11:40:58 +0800 Subject: [PATCH 6/6] More fixes --- docs/devGuide/writingComponents.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/devGuide/writingComponents.md b/docs/devGuide/writingComponents.md index 236d2e3c1a..2763460a58 100644 --- a/docs/devGuide/writingComponents.md +++ b/docs/devGuide/writingComponents.md @@ -23,7 +23,7 @@ The main logic of the node processing flow can be found in [`packages/core/src/h A MarkBind source file is first parsed into a series of nodes. -In general, each component will be parsed as a node, which may contain other childnodes (components or otherwise). +In general, each component will be parsed as a node, which may contain other child nodes (components or otherwise). Each node is then processed to implement MarkBind functionalities, such as checking for invalid intrasite links and rendering markdown. Components may undergo further processing (in `processNode`) and/or post-processing (in `postProcessNode`) to further transform the node to the desired HTML. @@ -83,10 +83,10 @@ Hence, MarkBind slots can be accessed in a Vue component either through the [nam ### As a Plugin MarkBind components can be implemented as a plugin as well. -This is suitable for more lightweight components where the implementation is largely in processing the node, where it is fitting to use MarkBind plugins' `processNode` or `postRender` interfaces. +This is suitable for more lightweight components where the implementation is largely in processing the node, making it fitting to use MarkBind plugins' `processNode` or `postRender` interfaces. These interfaces provide additional entry points for modifying the page generated, and do not replace MarkBind's usual node processing. -The [Writing Plugins]({{baseUrl}}/devGuide/writingPlugins.html) guide is a good place to get started on plugins. +The [Writing Plugins]({{baseUrl}}/devGuide/writingPlugins.html) page is a good guide to get started on plugins. {{ icon_examples }} * The [`tree` component](https://github.com/MarkBind/markbind/blob/master/packages/core/src/plugins/default/markbind-plugin-tree.js) is implemented as a default plugin @@ -117,10 +117,10 @@ Components should be compatible with SSR (Server-Side Rendering). Minimally, there should be no SSR issues (viewable from the browser console), though a lack of warnings does **not** mean that there are no SSR problems. A guide on SSR for MarkBind can be found [here]({{baseUrl}}/devGuide/design/serverSideRendering.html). -Vue-component-specific tips for resolving SSR issues: +Vue-specific tips for resolving SSR issues: * The `mount` and `beforeMount` lifecycle hooks will only be executed on the client, not the server * When using `v-if`, ensure that it will evaluate to the same value on both the client and server -* Take note of how the Vue component will be compiled, ensure that the HTML is correct and aligns on both client- and server- side +* Take note of how the Vue component will be compiled, ensuring that the HTML is correct and aligns on both client- and server- side * Conditionally render data when it has been fully loaded #### Bundle size @@ -134,7 +134,7 @@ Ideally, this should not increase MarkBind's bundle size too much. When choosing to use a third-party library or package, it should ideally be well-maintained and not have too many dependencies. While dependencies may be inevitable, a package with dependencies on large libraries may lag behind the most recent releases of these libraries, which may become a blocker for MarkBind to migrate to these recent releases as well. -For instance, if `bootstrap-vue` depends on Bootstrap and Vue, MarkBind will need to wait for `bootstrap-vue` to migrate to the newest versions of both Bootstrap and Vue before MarkBind can migrate to these versions of Bootstrap and Vue as well. +For instance, if `bootstrap-vue` depends on Bootstrap and Vue, we will need to wait for `bootstrap-vue` to migrate to the newest versions of both Bootstrap and Vue before MarkBind can migrate to these versions of Bootstrap and Vue as well.