From 930541e7265602e37d07deb2b7b576e619078a58 Mon Sep 17 00:00:00 2001
From: harrywan <445436867@qq.com>
Date: Wed, 9 Mar 2022 10:27:11 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B5=E9=9D=A2=E6=94=AF=E6=8C=81tsx?=
 =?UTF-8?q?=EF=BC=8C=E6=8F=90=E4=BE=9BdefineRoute=E9=85=8D=E7=BD=AEtsx?=
 =?UTF-8?q?=E5=92=8Cjsx=E7=9A=84route=20(#106)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: 页面支持tsx,提供defineRoute配置tsx和jsx的route

* .vue文件也支持defineRouteMeta

* feat: 改回声明用法
---
 .../src/plugins/misc/route/index.js           | 44 ++++++++++++----
 .../plugins/misc/route/template/routes.tpl    |  4 ++
 packages/fes-template-h5/src/pages/index.vue  | 50 ++++++++++++-------
 .../fes-template-h5/src/pages/onepiece.vue    |  2 +-
 packages/fes-template-h5/src/pages/test.jsx   |  7 ---
 packages/fes-template-h5/src/pages/test.tsx   | 15 ++++++
 packages/fes-utils/package.json               |  1 +
 packages/fes-utils/src/index.js               |  4 +-
 packages/fes/types.d.ts                       |  7 ++-
 9 files changed, 96 insertions(+), 38 deletions(-)
 delete mode 100644 packages/fes-template-h5/src/pages/test.jsx
 create mode 100644 packages/fes-template-h5/src/pages/test.tsx

diff --git a/packages/fes-preset-built-in/src/plugins/misc/route/index.js b/packages/fes-preset-built-in/src/plugins/misc/route/index.js
index 272d662e..f963cb00 100644
--- a/packages/fes-preset-built-in/src/plugins/misc/route/index.js
+++ b/packages/fes-preset-built-in/src/plugins/misc/route/index.js
@@ -2,7 +2,7 @@ import { readdirSync, statSync, readFileSync } from 'fs';
 import {
     join, extname, posix, basename
 } from 'path';
-import { lodash } from '@fesjs/utils';
+import { lodash, parser, generator } from '@fesjs/utils';
 import { parse } from '@vue/compiler-sfc';
 import { Logger } from '@fesjs/compiler';
 import { runtimePath } from '../../../utils/constants';
@@ -21,7 +21,7 @@ const logger = new Logger('fes:router');
 
 const isProcessFile = function (path) {
     const ext = extname(path);
-    return statSync(path).isFile() && ['.vue', '.jsx'].includes(ext);
+    return statSync(path).isFile() && ['.vue', '.jsx', '.tsx'].includes(ext);
 };
 
 const isProcessDirectory = function (path, item) {
@@ -71,6 +71,19 @@ const getRoutePath = function (parentRoutePath, fileName) {
     return posix.join(parentRoutePath, fileName);
 };
 
+function getRouteMeta(content) {
+    const ast = parser.parse(content, {
+        sourceType: 'module',
+        plugins: ['jsx', 'typescript']
+    });
+    const defineRouteExpression = ast.program.body.filter(expression => expression.type === 'ExpressionStatement' && expression.expression.type === 'CallExpression' && expression.expression.callee.name === 'defineRouteMeta')[0];
+    if (defineRouteExpression) {
+        const argument = generator(defineRouteExpression.expression.arguments[0]);
+        return JSON.parse(argument.code.replace(/'/g, '"').replace(/(\S+):/g, (global, m1) => `"${m1}":`));
+    }
+    return null;
+}
+
 let cacheGenRoutes = {};
 
 // TODO 约定 layout 目录作为布局文件夹,
@@ -88,16 +101,12 @@ const genRoutes = function (parentRoutes, path, parentRoutePath, config) {
         // 文件或者目录的绝对路径
         const component = join(path, item);
         if (isProcessFile(component)) {
-            const { descriptor } = parse(readFileSync(component, 'utf-8'));
-            const routeMetaBlock = descriptor.customBlocks.find(
-                b => b.type === 'config'
-            );
             const ext = extname(item);
             const fileName = basename(item, ext);
             // 路由的path
             const routePath = getRoutePath(parentRoutePath, fileName);
             if (cacheGenRoutes[routePath]) {
-                logger.warn(`[WARNING]: The file path: ${routePath}(.jsx/.vue) conflict in router,will only use ${routePath}.jsx,please remove one of.`);
+                logger.warn(`[WARNING]: The file path: ${routePath}(.jsx/.tsx/.vue) conflict in router,will only use ${routePath}.tsx or ${routePath}.jsx,please remove one of.`);
                 return;
             }
             cacheGenRoutes[routePath] = true;
@@ -105,7 +114,24 @@ const genRoutes = function (parentRoutes, path, parentRoutePath, config) {
             // 路由名称
             const routeName = getRouteName(parentRoutePath, fileName);
             const componentPath = getComponentPath(parentRoutePath, fileName, config);
-            const routeMeta = routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {};
+
+            let content = readFileSync(component, 'utf-8');
+            let routeMeta = {};
+            if (ext === '.vue') {
+                const { descriptor } = parse(content);
+                const routeMetaBlock = descriptor.customBlocks.find(
+                    b => b.type === 'config'
+                );
+                routeMeta = routeMetaBlock?.content ? JSON.parse(routeMetaBlock.content) : {};
+                if (descriptor.script) {
+                    content = descriptor.script.content;
+                    routeMeta = getRouteMeta(content) || routeMeta;
+                }
+            }
+            if (ext === '.jsx' || ext === '.tsx') {
+                routeMeta = getRouteMeta(content) || {};
+            }
+
             const routeConfig = {
                 path: routePath,
                 component: componentPath,
@@ -302,7 +328,7 @@ export default function (api) {
 
     api.addCoreExports(() => [
         {
-            specifiers: ['getRoutes', 'getRouter', 'getHistory', 'destroyRouter'],
+            specifiers: ['getRoutes', 'getRouter', 'getHistory', 'destroyRouter', 'defineRouteMeta'],
             source: absCoreFilePath
         }
     ]);
diff --git a/packages/fes-preset-built-in/src/plugins/misc/route/template/routes.tpl b/packages/fes-preset-built-in/src/plugins/misc/route/template/routes.tpl
index e6a6c6aa..6d2083d4 100644
--- a/packages/fes-preset-built-in/src/plugins/misc/route/template/routes.tpl
+++ b/packages/fes-preset-built-in/src/plugins/misc/route/template/routes.tpl
@@ -60,3 +60,7 @@ export const destroyRouter = ()=>{
     router = null;
     history = null;
 }
+
+export const defineRouteMeta = (param)=>{
+    return param
+}
diff --git a/packages/fes-template-h5/src/pages/index.vue b/packages/fes-template-h5/src/pages/index.vue
index 8cf4ebc3..7d6479dc 100644
--- a/packages/fes-template-h5/src/pages/index.vue
+++ b/packages/fes-template-h5/src/pages/index.vue
@@ -1,26 +1,30 @@
 <template>
     <div class="onepiece m-10px text-green">
         fes h5 & 拉夫德鲁<br />
-        <fes-icon :spin="true" class="one-icon" type="smile" @click="clickIcon" />
+        <fes-icon
+            :spin="true"
+            class="one-icon"
+            type="smile"
+            @click="clickIcon"
+        />
         <HelloWorld />
         <HelloTSX />
         <helloTS />
     </div>
 </template>
-<config>
-{
-    "title": "首页",
-    "name": "testIndex",
-    "layout": "false"
-}
-</config>
 <script>
 import { ref } from 'vue';
-import { request } from '@fesjs/fes';
+import { request, defineRouteMeta, useRoute } from '@fesjs/fes';
 import HelloWorld from '@/components/helloWorld';
 import HelloTSX from '@/components/helloTSX';
 import helloTS from '@/components/helloTS';
 
+defineRouteMeta({
+    title: '首页',
+    name: 'testIndex',
+    layout: false
+});
+
 export default {
     components: {
         HelloWorld,
@@ -33,6 +37,7 @@ export default {
         const clickIcon = () => {
             console.log('click Icon');
         };
+        console.log(useRoute());
         // request('/api', null, {
         // }).then((res) => {
         //     console.log(res);
@@ -59,15 +64,23 @@ export default {
         // });
 
         const get = (id) => {
-            request('/get/api', { id }, {
-                method: 'get'
-            });
+            request(
+                '/get/api',
+                { id },
+                {
+                    method: 'get'
+                }
+            );
         };
 
         const post = (id) => {
-            request('/api', { id }, {
-                responseType: 'blob'
-            }).then((data) => {
+            request(
+                '/api',
+                { id },
+                {
+                    responseType: 'blob'
+                }
+            ).then((data) => {
                 console.log(data);
             });
         };
@@ -80,7 +93,6 @@ export default {
         // post(2);
         post(3);
 
-
         // setTimeout(() => {
         //     request('/api', null, {
         //         throttle: 3000,
@@ -138,8 +150,8 @@ export default {
 </script>
 
 <style lang="less" scoped>
-@import "~@/styles/mixins/hairline";
-@import "~@/styles/mixins/hover";
+@import '~@/styles/mixins/hairline';
+@import '~@/styles/mixins/hover';
 
 div {
     padding: 20px;
@@ -154,6 +166,6 @@ div {
 }
 .onepiece {
     text-align: center;
-    .hairline("top");
+    .hairline('top');
 }
 </style>
diff --git a/packages/fes-template-h5/src/pages/onepiece.vue b/packages/fes-template-h5/src/pages/onepiece.vue
index bcf8dafd..e8c335c6 100644
--- a/packages/fes-template-h5/src/pages/onepiece.vue
+++ b/packages/fes-template-h5/src/pages/onepiece.vue
@@ -5,7 +5,7 @@
 <config>
 {
     "title": "onepiece",
-    "layout": "true"
+    "layout": true
 }
 </config>
 <script>
diff --git a/packages/fes-template-h5/src/pages/test.jsx b/packages/fes-template-h5/src/pages/test.jsx
deleted file mode 100644
index 84472aed..00000000
--- a/packages/fes-template-h5/src/pages/test.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { defineComponent } from 'vue';
-
-export default defineComponent({
-    setup() {
-        return () => <div>hello jsx</div>;
-    }
-});
diff --git a/packages/fes-template-h5/src/pages/test.tsx b/packages/fes-template-h5/src/pages/test.tsx
new file mode 100644
index 00000000..07efe642
--- /dev/null
+++ b/packages/fes-template-h5/src/pages/test.tsx
@@ -0,0 +1,15 @@
+import { defineRouteMeta, useRoute } from '@fesjs/fes';
+import { defineComponent } from 'vue';
+
+defineRouteMeta({
+    title: 'test',
+    name: 'test'
+})
+
+export default defineComponent({
+    setup() {
+        const route = useRoute();
+        console.log(route)
+        return () => <div>hello tsx</div>;
+    }
+});
diff --git a/packages/fes-utils/package.json b/packages/fes-utils/package.json
index e1020935..ac5bbe32 100644
--- a/packages/fes-utils/package.json
+++ b/packages/fes-utils/package.json
@@ -25,6 +25,7 @@
   },
   "dependencies": {
     "@babel/parser": "^7.15.0",
+    "@babel/generator": "^7.15.0",
     "@babel/traverse": "^7.15.0",
     "chalk": "^4.1.2",
     "chokidar": "^3.5.2",
diff --git a/packages/fes-utils/src/index.js b/packages/fes-utils/src/index.js
index 816691c2..c7967646 100644
--- a/packages/fes-utils/src/index.js
+++ b/packages/fes-utils/src/index.js
@@ -8,6 +8,7 @@ import glob from 'glob';
 import createDebug from 'debug';
 import * as parser from '@babel/parser';
 import traverse from '@babel/traverse';
+import generator from '@babel/generator';
 import rimraf from 'rimraf';
 import mkdirp from 'mkdirp';
 import pkgUp from 'pkg-up';
@@ -40,7 +41,8 @@ export {
     traverse,
     pkgUp,
     portfinder,
-    resolve
+    resolve,
+    generator
 };
 
 export {
diff --git a/packages/fes/types.d.ts b/packages/fes/types.d.ts
index 89ad8bf8..1a880b6b 100644
--- a/packages/fes/types.d.ts
+++ b/packages/fes/types.d.ts
@@ -1,5 +1,10 @@
-
 // @ts-ignore
 export * from '@@/core/coreExports';
 // @ts-ignore
 export * from '@@/core/pluginExports';
+
+export declare function defineRouteMeta(routeMeta: {
+    name?: string;
+    title?: string;
+    layout?: boolean | { sidebar?: boolean; header?: boolean; logo?: boolean };
+});