Repository: speee/jsx-slack
Pull request #127 (2020-03-12 02:47:18)

Update behavior of jsxslack template literal tag
Now changed the behavior of `jsxslack` tag to return JSX element, or JSON if serializable.

Because tags have already supported fragments, `jsxslack.fragment` has marked as deprecated due to confusable name. The exactly same behavior is now provided in newly added `jsxslack.raw`.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a43f84..bd9f967 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@
 
 - Improve escaping special characters to keep original character as possible ([#124](https://github.com/speee/jsx-slack/issues/124), [#125](https://github.com/speee/jsx-slack/pull/125))
 - Make JSX element for passing to Slack API serializable to JSON directly ([#126](https://github.com/speee/jsx-slack/pull/126))
+- `jsxslack` template literal tag now returns raw JSX element, or JSON if serializable ([#127](https://github.com/speee/jsx-slack/pull/127))
+
+### Added
+
+- `jsxslack.raw` template literal tag to generate JSX element always ([#127](https://github.com/speee/jsx-slack/pull/127))
+
+### Deprecated
+
+- Confusable `jsxslack.fragment` template literal tag has deprecated (Use `jsxslack` or `jsxslack.raw` instead) ([#127](https://github.com/speee/jsx-slack/pull/127))
 
 ## v1.4.0 - 2020-03-06
 
diff --git a/README.md b/README.md
index 4a32cc3..293673a 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ yarn add @speee-js/jsx-slack
 
 ### Quick start: Template literal
 
-Do you hate troublesome setting up for JSX? All right. We provide **`jsxslack`** tagged template literal to build blocks _right out of the box_.
+Do you hate troublesome setting up for JSX? All right. We provide **`jsxslack`** tagged template literal to build blocks _right out of the box_. It will generate JSX element, or JSON for Slack API if serializable.
 
 It allows the template syntax almost same as JSX, powered by [HTM (Hyperscript Tagged Markup)](https://github.com/developit/htm). Setting for transpiler and importing built-in components are not required.
 
@@ -295,15 +295,15 @@ const Header = ({ children }) => (
 
 > :warning: TypeScript cannot customize the factory method for fragment syntax. ([Microsoft/TypeScript#20469](https://github.com/Microsoft/TypeScript/issues/20469)) Please use `<Fragment>` component as usual.
 
-### `jsxslack.fragment` template literal tag
+### In the case of template literal tag
 
-You should use **`jsxslack.fragment`** template literal tag instead of `jsxslack` when you want to create HOC or custom block with prefering template literal to JSX transpiler.
+`jsxslack` template literal tag has [built-in fragments support](https://github.com/developit/htm#improvements-over-jsx) so `<Fragment>` does not have to use.
 
 ```javascript
 // Header.js
 import { jsxslack } from '@speee-js/jsx-slack'
 
-const Header = ({ children }) => jsxslack.fragment`
+const Header = ({ children }) => jsxslack`
   <Section>
     <b>${children}</b>
   </Section>
@@ -312,8 +312,6 @@ const Header = ({ children }) => jsxslack.fragment`
 export default Header
 ```
 
-`<Fragment>` built-in component does not have to use because [the parser allows multiple elements on the root.](https://github.com/developit/htm#improvements-over-jsx)
-
 A defined component may use in `jsxslack` tag as below:
 
 ```javascript
@@ -332,6 +330,8 @@ console.log(jsxslack`
 
 Please notice to a usage of component that has a bit different syntax from JSX.
 
+> :bulb: **TIPS:** `jsxslack` tag will return JSX element, or JSON if the root element was serializable. You can use `jsxslack.raw` instead if you always want the raw JSX element.
+
 ## Similar projects
 
 - [slack-jsx](https://github.com/zcei/slack-jsx) - Compose Slack messages from JSX Components instead of writing JSON.
diff --git a/demo/index.js b/demo/index.js
index 7c3c791..5c01813 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -107,7 +107,15 @@ const setPreview = query => {
 
 const convert = () => {
   try {
-    const output = jsxslack([jsxEditor.getValue()])
+    const output = (() => {
+      const raw = jsxslack.raw([jsxEditor.getValue()])
+
+      if (!raw || !raw.toJSON)
+        throw new Error('The passed value cannot serialize to JSON.')
+
+      return raw.toJSON('')
+    })()
+
     const encoded = JSON.stringify(output).replace(/\+/g, '%2b')
 
     json.value = JSON.stringify(output, null, '  ')
diff --git a/src/jsx.ts b/src/jsx.ts
index e93a9fc..a612c72 100644
--- a/src/jsx.ts
+++ b/src/jsx.ts
@@ -118,7 +118,7 @@ export namespace JSXSlack {
     type: FC<P> | string | NodeType
     props: Props<P>
     children: Child<any>[]
-    toJSON?: () => any
+    toJSON?: (key?: string) => any
   }
 
   export const h = <P extends {}>(
diff --git a/src/tag.ts b/src/tag.ts
index 90bdbf5..a8aa7f0 100644
--- a/src/tag.ts
+++ b/src/tag.ts
@@ -11,6 +11,9 @@ type JSXSlackTemplate<T = any> = (
 ) => T
 
 interface JSXSlackTemplateTag extends JSXSlackTemplate {
+  readonly raw: JSXSlackTemplate
+
+  /** @deprecated jsxslack.fragment was deprecated and will remove in future version. Use jsxslack tag (or jsxslack.raw if failed). */
   readonly fragment: JSXSlackTemplate
 }
 
@@ -77,11 +80,29 @@ const parse = (template: TemplateStringsArray, ...substitutions: any[]) => {
   return Array.isArray(parsed) ? parsed.map(p => render(p)) : render(parsed)
 }
 
-const jsxslack: JSXSlackTemplateTag = Object.defineProperty(
-  (template: TemplateStringsArray, ...substitutions: any[]) =>
-    JSXSlack(parse(template, ...substitutions)),
-  'fragment',
-  { value: parse }
+const jsxslack: JSXSlackTemplateTag = Object.defineProperties(
+  (template: TemplateStringsArray, ...substitutions: any[]) => {
+    const parsed = parse(template, ...substitutions)
+
+    return parsed && typeof parsed.toJSON === 'function'
+      ? parsed.toJSON('')
+      : parsed
+  },
+  {
+    fragment: {
+      enumerable: true,
+      value: (template, ...substitutions) => {
+        console.warn(
+          '[DEPRECATION WARNING] jsxslack.fragment was deprecated and will remove in future version. Use jsxslack tag (or jsxslack.raw if failed).'
+        )
+        return parse(template, ...substitutions)
+      },
+    },
+    raw: {
+      enumerable: true,
+      value: parse,
+    },
+  }
 )
 
 export default jsxslack


Test patch:
diff --git a/test/tag.tsx b/test/tag.tsx
index 2b0e388..094e5c6 100644
--- a/test/tag.tsx
+++ b/test/tag.tsx
@@ -66,8 +66,7 @@ describe('Tagged template', () => {
         <Actions>
           <Select>
             ${[...Array(10)].map(
-              (_, i) =>
-                jsxslack.fragment`<Option value=${i.toString()}>${i}</Option>`
+              (_, i) => jsxslack`<Option value=${i.toString()}>${i}</Option>`
             )}
           </Select>
         </Actions>
@@ -93,8 +92,8 @@ describe('Tagged template', () => {
     const template = jsxslack`
       <Blocks>
         <Section>cond${'i'}tio${null}nal</Section>
-        ${true && jsxslack.fragment`<Section>rendering</Section>`}
-        ${false && jsxslack.fragment`<Section>test</Section>`}
+        ${true && jsxslack`<Section>rendering</Section>`}
+        ${false && jsxslack`<Section>test</Section>`}
       </Blocks>
     `
 
@@ -163,8 +162,48 @@ describe('Tagged template', () => {
     expect(templateRawEntitySection).toStrictEqual(jsxRawEntitySection)
   })
 
-  describe('jsxslack.fragment', () => {
-    it('returns raw nodes for reusable as component', () => {
+  describe('jsxslack.raw', () => {
+    it('always returns raw node(s) for reusable as component', () => {
+      const func = title => jsxslack.raw`
+        <Section><b>${title}</b></Section>
+        <Divider />
+      `
+
+      expect(func('test')).toStrictEqual(
+        <Fragment>
+          <Section>
+            <b>test</b>
+          </Section>
+          <Divider />
+        </Fragment>
+      )
+
+      const Component = ({ children }) => jsxslack.raw`
+        <Section><b>${children}</b></Section>
+        <Divider />
+      `
+
+      expect(jsxslack`<Blocks><${Component}>Hello<//></Blocks>`).toStrictEqual(
+        JSXSlack(
+          <Blocks>
+            <Section>
+              <b>Hello</b>
+            </Section>
+            <Divider />
+          </Blocks>
+        )
+      )
+
+      expect(
+        JSXSlack(jsxslack.raw`<Blocks><${Component}>Hello<//></Blocks>`)
+      ).toStrictEqual(jsxslack`<Blocks><${Component}>Hello<//></Blocks>`)
+
+      expect(jsxslack.raw`<${Component}>test<//>`).toStrictEqual(func('test'))
+    })
+  })
+
+  describe('[DEPRECATED] jsxslack.fragment', () => {
+    it('always returns raw node(s) for reusable as component', () => {
       const func = title => jsxslack.fragment`
         <Section><b>${title}</b></Section>
         <Divider />
@@ -195,6 +234,10 @@ describe('Tagged template', () => {
         )
       )
 
+      expect(
+        JSXSlack(jsxslack.fragment`<Blocks><${Component}>Hello<//></Blocks>`)
+      ).toStrictEqual(jsxslack`<Blocks><${Component}>Hello<//></Blocks>`)
+
       expect(jsxslack.fragment`<${Component}>test<//>`).toStrictEqual(
         func('test')
       )