@siven-li/elpis 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +55 -0
  3. package/README.md +201 -0
  4. package/app/controller/base.js +37 -0
  5. package/app/controller/project.js +65 -0
  6. package/app/controller/view.js +20 -0
  7. package/app/extend/logger.js +35 -0
  8. package/app/middleware/api-params-verify.js +97 -0
  9. package/app/middleware/api-sign-verify.js +30 -0
  10. package/app/middleware/error-handler.js +34 -0
  11. package/app/middleware/project-handler.js +22 -0
  12. package/app/middleware.js +37 -0
  13. package/app/pages/assets/custom.css +16 -0
  14. package/app/pages/boot.js +53 -0
  15. package/app/pages/common/curl.js +74 -0
  16. package/app/pages/common/utils.js +2 -0
  17. package/app/pages/dashboard/complex-view/header-view/complex-view/sub-menu/sub-menu.vue +35 -0
  18. package/app/pages/dashboard/complex-view/header-view/header-view.vue +132 -0
  19. package/app/pages/dashboard/complex-view/iframe-view/iframe-view.vue +45 -0
  20. package/app/pages/dashboard/complex-view/schema-view/complex-view/search-panel/search-panel.vue +37 -0
  21. package/app/pages/dashboard/complex-view/schema-view/complex-view/table-panel/table-panel.vue +130 -0
  22. package/app/pages/dashboard/complex-view/schema-view/components/component-config.js +23 -0
  23. package/app/pages/dashboard/complex-view/schema-view/components/create-form/create-form.vue +90 -0
  24. package/app/pages/dashboard/complex-view/schema-view/components/detail-panel/detail-panel.vue +110 -0
  25. package/app/pages/dashboard/complex-view/schema-view/components/edit-form/edit-form.vue +133 -0
  26. package/app/pages/dashboard/complex-view/schema-view/hook/schema.js +125 -0
  27. package/app/pages/dashboard/complex-view/schema-view/schema-view.vue +97 -0
  28. package/app/pages/dashboard/complex-view/sider-view/complex-view/sub-menu/sub-menu.vue +35 -0
  29. package/app/pages/dashboard/complex-view/sider-view/sider-view.vue +127 -0
  30. package/app/pages/dashboard/dashboard.vue +94 -0
  31. package/app/pages/dashboard/entry.dashboard.js +43 -0
  32. package/app/pages/store/index.js +3 -0
  33. package/app/pages/store/menu.js +59 -0
  34. package/app/pages/store/project.js +17 -0
  35. package/app/pages/widgets/header-container/assets/avatar.png +0 -0
  36. package/app/pages/widgets/header-container/assets/logo.png +0 -0
  37. package/app/pages/widgets/header-container/header-container.vue +124 -0
  38. package/app/pages/widgets/schema-form/complex-view/input/input.vue +143 -0
  39. package/app/pages/widgets/schema-form/complex-view/input-number/input-number.vue +141 -0
  40. package/app/pages/widgets/schema-form/complex-view/select/select.vue +127 -0
  41. package/app/pages/widgets/schema-form/form-item-config.js +22 -0
  42. package/app/pages/widgets/schema-form/schema-form.vue +129 -0
  43. package/app/pages/widgets/schema-search-bar/complex-view/date-range/date-range.vue +49 -0
  44. package/app/pages/widgets/schema-search-bar/complex-view/dynamic-select/dynamic-select.vue +65 -0
  45. package/app/pages/widgets/schema-search-bar/complex-view/input/input.vue +43 -0
  46. package/app/pages/widgets/schema-search-bar/complex-view/select/select.vue +50 -0
  47. package/app/pages/widgets/schema-search-bar/schema-search-bar.vue +133 -0
  48. package/app/pages/widgets/schema-search-bar/search-item-config.js +26 -0
  49. package/app/pages/widgets/schema-table/schema-table.vue +234 -0
  50. package/app/pages/widgets/sider-container/sider-container.vue +32 -0
  51. package/app/public/static/logo.png +0 -0
  52. package/app/public/static/normalize.css +239 -0
  53. package/app/router/project.js +6 -0
  54. package/app/router/view.js +9 -0
  55. package/app/router-schema/project.js +30 -0
  56. package/app/service/base.js +12 -0
  57. package/app/service/project.js +44 -0
  58. package/app/view/entry.tpl +26 -0
  59. package/app/webpack/config/webpack.base.js +194 -0
  60. package/app/webpack/config/webpack.dev.js +58 -0
  61. package/app/webpack/config/webpack.prod.js +114 -0
  62. package/app/webpack/dev.js +48 -0
  63. package/app/webpack/libs/blank.js +1 -0
  64. package/app/webpack/prod.js +18 -0
  65. package/config/config.default.js +3 -0
  66. package/elpis-core/env.js +20 -0
  67. package/elpis-core/index.js +88 -0
  68. package/elpis-core/loader/config.js +55 -0
  69. package/elpis-core/loader/controller.js +56 -0
  70. package/elpis-core/loader/extend.js +45 -0
  71. package/elpis-core/loader/middleware.js +58 -0
  72. package/elpis-core/loader/router-schema.js +38 -0
  73. package/elpis-core/loader/router.js +40 -0
  74. package/elpis-core/loader/service.js +56 -0
  75. package/index.js +39 -0
  76. package/model/index.js +104 -0
  77. package/package.json +93 -0
  78. package/test/controll/project.test.js +203 -0
@@ -0,0 +1,194 @@
1
+ const glob = require('glob');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const webpack = require('webpack');
5
+ const merge = require('webpack-merge');
6
+ const { VueLoaderPlugin } = require('vue-loader');
7
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
8
+
9
+ // 动态构造 elpisPageEntries elpisHtmlWebpackPluginList
10
+ const elpisPageEntries = {};
11
+ const elpisHtmlWebpackPluginList = [];
12
+ // 获取 elpis/app/pages/ 目录下所有入口文件(entry.xxx.js)
13
+ const elpisEntryFiles = glob.sync(path.resolve(__dirname, '../../pages/**/entry.*.js'));
14
+ // 遍历所有入口文件,构建 entry 配置和 HtmlWebpackPlugin 配置
15
+ elpisEntryFiles.forEach(file => {
16
+ handleFile(file, elpisPageEntries, elpisHtmlWebpackPluginList);
17
+ });
18
+
19
+ // 动态构造 businessPageEntries businessHtmlWebpackPluginList
20
+ const businessPageEntries = {};
21
+ const businessHtmlWebpackPluginList = [];
22
+ // 获取 business/app/pages/ 目录下所有入口文件(entry.xxx.js)
23
+ const businessEntryFiles = glob.sync(path.resolve(process.cwd(), './app/pages/**/entry.*.js'));
24
+ // 遍历所有入口文件,构建 entry 配置和 HtmlWebpackPlugin 配置
25
+ businessEntryFiles.forEach(file => {
26
+ handleFile(file, businessPageEntries, businessHtmlWebpackPluginList);
27
+ });
28
+
29
+ // 构造相关 webpack 处理的数据机构
30
+ function handleFile(entryFile, entries = {}, htmlWebpackPluginList = []) {
31
+ const entryName = path.basename(entryFile, '.js');
32
+ entries[entryName] = entryFile;
33
+ htmlWebpackPluginList.push(
34
+ new HtmlWebpackPlugin({
35
+ // 指定要使用的模板
36
+ template: path.resolve(__dirname, '../../view/entry.tpl'),
37
+ // 输出文件的路径
38
+ filename: path.resolve(process.cwd(), './app/public/dist/', `${entryName}.tpl`),
39
+ // 要注入的代码块
40
+ chunks: [entryName]
41
+ }));
42
+ }
43
+
44
+ // 加载 业务 webpack 配置
45
+ let businessWebpackConfig = {};
46
+ try {
47
+ businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`);
48
+ } catch (e) {}
49
+
50
+ /**
51
+ * 基础配置
52
+ */
53
+ module.exports = merge.smart({
54
+ // 入口文件配置
55
+ entry: Object.assign({}, elpisPageEntries, businessPageEntries),
56
+ // 输出文件配置(决定了打包后的文件输出到哪里)
57
+ output: {},
58
+ // 模块解析配置(决定了要加载解析哪些模块,以及如何解析)
59
+ module: {
60
+ rules: [
61
+ {
62
+ test: /\.vue$/,
63
+ use: {
64
+ loader: require.resolve('vue-loader')
65
+ }
66
+ }, {
67
+ test: /\.js$/,
68
+ // 只对业务代码进行 babel 转换,加快打包速度
69
+ include: [
70
+ // 处理 elpis 目录
71
+ path.resolve(__dirname, '../../pages'),
72
+ // 处理 业务 目录
73
+ path.resolve(process.cwd(), './app/pages')
74
+ ],
75
+ use: {
76
+ loader: require.resolve('babel-loader'),
77
+ }
78
+ }, {
79
+ test: /\.(png|jpe?g|gif|svg|ttf|eot|woff2?)$/,
80
+ type: 'asset', // Webpack 5 内置的 Asset Modules, 等同于 url-loader 带 limit
81
+ parser: {
82
+ dataUrlCondition: {
83
+ maxSize: 300 * 1024
84
+ }
85
+ }
86
+ }, {
87
+ test: /\.css$/,
88
+ use: [require.resolve('style-loader'), require.resolve('css-loader')]
89
+ }, {
90
+ test: /\.less$/,
91
+ use: [require.resolve('style-loader'), require.resolve('css-loader'), require.resolve('less-loader')]
92
+ }
93
+ ]
94
+ },
95
+ // 配置模块解析的具体行为(定义 webpack 在打包时,如何找到并解析具体模块的路径)
96
+ resolve: {
97
+ extensions: ['.js', '.vue', 'json', '.css', '.less'],
98
+ alias: (() => {
99
+ const aliasMap = {};
100
+ const blankModulePath = path.resolve(__dirname, '../libs/blank.js');
101
+
102
+ // dashboard 路由扩展配置
103
+ const businessDashboardRouterConfig = path.resolve(process.cwd(), './app/pages/dashboard/router.js');
104
+ aliasMap['$businessDashboardRouterConfig'] = fs.existsSync(businessDashboardRouterConfig) ? businessDashboardRouterConfig : blankModulePath;
105
+
106
+ // schema-view component 扩展配置
107
+ const businessComponentConfig = path.resolve(process.cwd(), './app/pages/dashboard/complex-view/schema-view/components/component-config.js');
108
+ aliasMap['$businessComponentConfig'] = fs.existsSync(businessComponentConfig) ? businessComponentConfig : blankModulePath;
109
+
110
+ // schema-schema-form 扩展配置
111
+ const businessFromItmeConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-form/form-item-config.js');
112
+ aliasMap['$businessFromItmeConfig'] = fs.existsSync(businessFromItmeConfig) ? businessFromItmeConfig : blankModulePath;
113
+
114
+ // search-bar 扩展配置
115
+ const businessSearchItemConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-search-bar/search-item-config.js');
116
+ aliasMap['$businessSearchItemConfig'] = fs.existsSync(businessSearchItemConfig) ? businessSearchItemConfig : blankModulePath;
117
+
118
+ return {
119
+ // 创建 import 或 require 的别名,来确保模块引入变得更简单
120
+ 'vue': require.resolve('vue'),
121
+ '@babel/runtime/helpers/asyncToGenerator': require.resolve('@babel/runtime/helpers/asyncToGenerator'),
122
+ '@babel/runtime/helpers/toConsumableArray': require.resolve('@babel/runtime/helpers/toConsumableArray'),
123
+ '@babel/runtime/regenerator': require.resolve('@babel/runtime/regenerator'),
124
+ '$elpisPages': path.resolve(__dirname, '../../pages'),
125
+ '$elpisCommon': path.resolve(__dirname, '../../pages/common'),
126
+ '$elpisCurl': path.resolve(__dirname, '../../pages/common/curl.js'),
127
+ '$elpisUtils': path.resolve(__dirname, '../../pages/common/utils.js'),
128
+ '$elpisWidgets': path.resolve(__dirname, '../../pages/widgets'),
129
+ '$elpisHeaderContainer': path.resolve(__dirname, '../../pages/widgets/header-container/header-container.vue'),
130
+ '$elpisSchemaForm': path.resolve(__dirname, '../../pages/widgets/schema-form/schema-form.vue'),
131
+ '$elpisSchemaSearchBar': path.resolve(__dirname, '../../pages/widgets/schema-search-bar/schema-search-bar.vue'),
132
+ '$elpisSchemaTable': path.resolve(__dirname, '../../pages/widgets/schema-table/schema-table.vue'),
133
+ '$elpisSiderContainer': path.resolve(__dirname, '../../pages/widgets/sider-container/sider-container.vue'),
134
+ '$elpisStore': path.resolve(__dirname, '../../pages/store'),
135
+ '$elpisBoot': path.resolve(__dirname, '../../pages/boot.js'),
136
+ ...aliasMap
137
+ }
138
+ })()
139
+ },
140
+ // 配置 webpack 插件
141
+ plugins: [
142
+ // 复制你在 webpack 配置中定义的适用于普通文件(如 .js、.css)的规则,并将它们应用到 .vue 文件内部的对应语言块上
143
+ // 这样就可以在 .vue 文件中正常使用 <style> 和 <script>,并且享受 webpack 的完整功能
144
+ new VueLoaderPlugin(),
145
+ // 把第三方库暴露到 window context 下
146
+ new webpack.ProvidePlugin({
147
+ Vue: 'vue',
148
+ axios: 'axios',
149
+ _: 'lodash'
150
+ }),
151
+ // 定义全局常量
152
+ new webpack.DefinePlugin({
153
+ __VUE_OPTIONS_API__: 'true', // 支持 vue 解析 options api
154
+ __VUE_PROD_DEVTOOLS__: 'false', // 禁用 vue 调试工具
155
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' // 禁用生产环境显示 ”水合“ 信息
156
+ }),
157
+ // 构造最终渲染的页面模板
158
+ ...elpisHtmlWebpackPluginList,
159
+ ...businessHtmlWebpackPluginList
160
+ ],
161
+ // 配置打包输出优化(代码分割、压缩、模块合并、缓存、Tree Shaking等)
162
+ optimization: {
163
+ /**
164
+ * 代码分割 把 js 打包成3种类型
165
+ * 1. vendor: 第三方 lib 库,基本不会改动,除非依赖版本升级
166
+ * 2. common: 业务组件的公共模块,如 utils、config 等
167
+ * 3. entry.{page}: 页面 entry 里的业务组件代码,会经常改动
168
+ * 目的:把带动和引用频率不一样的 js 区分出来,以达到更好的利用浏览器缓存的效果
169
+ */
170
+ splitChunks: {
171
+ chunks: 'all', // 对同步异步模块都进行分割
172
+ maxAsyncRequests: 10, // 最大异步加载并行请求数
173
+ maxInitialRequests: 10, // 入口点最大加载并行请求数
174
+ cacheGroups: {
175
+ vendor: {
176
+ test: /[\\/]node_modules[\\/]/, // 通过 test 筛选出 node_modules 里的模块
177
+ name: 'vandor', // 打包后的文件名
178
+ priority: 20, // 优先级,数字越大,优先级越高,确保模块优先被归入此组
179
+ reuseExistingChunk: true, // 允许复用已存在的 chunk,避免重复打包
180
+ enforce: true // 强制执行,忽略 minSize 等限制,即使很小也提取出来
181
+ },
182
+ common: {
183
+ name: 'common',
184
+ minChunks: 2, // 至少被2个模块引入才提取到 common 中
185
+ minSize: 1, // 最小分割文件大小(1 byte),
186
+ priority: 10,
187
+ reuseExistingChunk: true
188
+ }
189
+ }
190
+ },
191
+ // 将 webpack 运行时生成的代码打包到 runtime.js
192
+ runtimeChunk: true
193
+ }
194
+ }, businessWebpackConfig);
@@ -0,0 +1,58 @@
1
+ const path = require('path');
2
+ const webpack = require('webpack');
3
+ const merge = require('webpack-merge');
4
+
5
+ // 基础配置
6
+ const baseConfig = require('./webpack.base');
7
+ // devServer 配置
8
+ const DEV_SERVER_CONFIG = {
9
+ HOST: '127.0.0.1',
10
+ PORT: 9002,
11
+ HMR_PATH: '__webpack_hmr',
12
+ TIMEOUT: 20000
13
+ }
14
+
15
+ const { HOST, PORT, HMR_PATH, TIMEOUT } = DEV_SERVER_CONFIG;
16
+
17
+ // 开发阶段的 entry 配置需要加入 hmr
18
+ Object.keys(baseConfig.entry).forEach(v => {
19
+ // 第三方包不作为 hmr 入口
20
+ if (v !== 'vendor') {
21
+ baseConfig.entry[v] = [
22
+ // 主入口文件
23
+ baseConfig.entry[v],
24
+ // hmr 更新入口,官方指定的 hmr 路径
25
+ `${require.resolve('webpack-hot-middleware/client')}?path=http://${HOST}:${PORT}/${HMR_PATH}&timeout=${TIMEOUT}&reload=true`,
26
+ ]
27
+ }
28
+ })
29
+
30
+ // dev环境 webpack 配置
31
+ const webpackConfig = merge(baseConfig, {
32
+ mode: 'development',
33
+ // source map 配置
34
+ devtool: 'eval-cheap-module-source-map',
35
+ // 输出文件配置
36
+ output: {
37
+ filename: 'js/[name]_[chunkhash:8].bundle.js',
38
+ path: path.resolve(process.cwd(), 'app/public/dist/dev'), // 输出文件存储路径
39
+ publicPath: `http://${HOST}:${PORT}/public/dist/dev/`, // 外部资源公共路径
40
+ globalObject: 'this', // 全局对象配置
41
+ },
42
+ // 开发阶段插件
43
+ plugins: [
44
+ // HotModuleReplacementPlugin 用于实现热模块替换 (Hot Module ReplacementPlugin 简称 HMR)
45
+ // 模块热替换允许在应用程序运行时替换模块
46
+ // 能让应用程序一直保持运行状态,极大的提高开发效率
47
+ new webpack.HotModuleReplacementPlugin({
48
+ multiStep: false
49
+ })
50
+ ]
51
+ });
52
+
53
+ module.exports = {
54
+ // devServer 配置,暴露给 dev.js 使用
55
+ DEV_SERVER_CONFIG,
56
+ // webpack 配置
57
+ webpackConfig
58
+ };
@@ -0,0 +1,114 @@
1
+ const merge = require('webpack-merge');
2
+ const path = require('path');
3
+ const Happypack = require('happypack');
4
+ const os = require('os');
5
+
6
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
7
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
8
+ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
9
+ const HtmlWebpackInjectAttributesPlugin = require('html-webpack-inject-attributes-plugin');
10
+ const TerserPlugin = require('terser-webpack-plugin');
11
+
12
+ // 多线程 build 设置
13
+ const happypackCommonConfig = {
14
+ debug: false,
15
+ threadPool: Happypack.ThreadPool({ size: os.cpus().length })
16
+ };
17
+
18
+
19
+ // 基础配置
20
+ const baseConfig = require('./webpack.base');
21
+ // 生产环境 webpack 配置
22
+ const webpackConfig = merge(baseConfig, {
23
+ // 指定生产环境
24
+ mode: 'production',
25
+ // 输出文件配置
26
+ output: {
27
+ filename: 'js/[name]_[chunkhash:8].bundle.js',
28
+ path: path.join(process.cwd(), './app/public/dist/prod/'),
29
+ publicPath: '/dist/prod/',
30
+ crossOriginLoading: 'anonymous' // 允许跨域加载
31
+ },
32
+ // 模块解析配置
33
+ module: {
34
+ rules: [{
35
+ test: /\.css$/,
36
+ use: [
37
+ MiniCssExtractPlugin.loader,
38
+ `${require.resolve('happypack/loader')}?id=css`
39
+ ]
40
+ }, {
41
+ test: /\.js$/,
42
+ // 只对业务代码进行 babel 转换,加快打包速度
43
+ include: [
44
+ // 处理 elpis 目录
45
+ path.resolve(__dirname, '../../../pages'),
46
+ // 处理 业务 目录
47
+ path.resolve(process.cwd(), './app/pages')
48
+ ],
49
+ use: [
50
+ `${require.resolve('happypack/loader')}?id=js`
51
+ ]
52
+ }]
53
+ },
54
+ performance: {
55
+ hints: false
56
+ },
57
+ plugins: [
58
+ // 每次 build 前清空 dist 目录
59
+ new CleanWebpackPlugin(['public/dist'], {
60
+ root: path.resolve(process.cwd(), './app/'),
61
+ exclude: [],
62
+ verbose: true,
63
+ dry: false
64
+ }),
65
+ // 提取公共 css,有效利用缓存
66
+ new MiniCssExtractPlugin({
67
+ chunkFilename: 'css/[name]_[contenthash:8].bundle.css'
68
+ }),
69
+ // 优化并压缩 css 资源
70
+ new CssMinimizerPlugin(),
71
+ // 多线程打包 js,加快打包速度
72
+ new Happypack({
73
+ ...happypackCommonConfig,
74
+ id: 'js',
75
+ loaders: [`${require.resolve('babel-loader')}?${JSON.stringify({
76
+ presets: [require.resolve('@babel/preset-env')],
77
+ plugins: [require.resolve('@babel/plugin-transform-runtime')]
78
+ })}`]
79
+ }),
80
+ // 多线程打包 css,加快打包速度
81
+ new Happypack({
82
+ ...happypackCommonConfig,
83
+ id: 'css',
84
+ loaders: [{
85
+ path: require.resolve('css-loader'),
86
+ options: {
87
+ importLoaders: 1
88
+ }
89
+ }]
90
+ }),
91
+ // 浏览器在请求资源时不发送用户的身份凭证
92
+ new HtmlWebpackInjectAttributesPlugin({
93
+ crossorigin: 'anonymous'
94
+ })
95
+ ],
96
+ optimization: {
97
+ // 使用 TerserPlugin 的并发和缓存,提升压缩阶段的性能
98
+ // 清除 console.log 等无用代码
99
+ minimize: true,
100
+ minimizer: [
101
+ new TerserPlugin({
102
+ parallel: true, // 利用多核 CPU 来提升压缩速度
103
+ terserOptions: {
104
+ compress: {
105
+ drop_console: true // 清除 console.log 等无用代码
106
+ },
107
+ sourceMap: false // 不生成 sourceMap 文件
108
+ }
109
+ })
110
+ ]
111
+ }
112
+ });
113
+
114
+ module.exports = webpackConfig;
@@ -0,0 +1,48 @@
1
+ // 本地开发启动 devServer
2
+ const express = require('express');
3
+ const path = require('path');
4
+ const consoler = require('consoler');
5
+ const webpack = require('webpack');
6
+ const devMiddleware = require('webpack-dev-middleware');
7
+ const hotMiddleware = require('webpack-hot-middleware');
8
+
9
+ module.exports = () => {
10
+ // 从 webpack.dev.js 中获取 webpack 配置和 devServer 配置
11
+ const { webpackConfig, DEV_SERVER_CONFIG } = require('./config/webpack.dev');
12
+
13
+ const app = express();
14
+ const compiler = webpack(webpackConfig);
15
+
16
+ // 指定静态文件目录
17
+ app.use(express.static(path.join(__dirname, '../public/dist')))
18
+ // 引用 devMiddlerware 中间件(监控文件改动)
19
+ app.use(devMiddleware(compiler, {
20
+ // 落地文件
21
+ writeToDisk: (filePath) => filePath.endsWith('.tpl'),
22
+ // 资源路径
23
+ publicPath: webpackConfig.output.publicPath,
24
+ // headers 配置
25
+ headers: {
26
+ 'Access-Control-Allow-Origin': '*',
27
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, Accept, X-Requested-With',
28
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS'
29
+ },
30
+ stats: {
31
+ colors: true
32
+ }
33
+ }));
34
+ // 引用 hotMiddleware 中间件(实现热更新通讯)
35
+ app.use(hotMiddleware(compiler, {
36
+ path: `/${DEV_SERVER_CONFIG.HMR_PATH}`,
37
+ log: () => { }
38
+ }));
39
+
40
+ consoler.info('请等待webpack初次构建完成...')
41
+
42
+ // 启动 devServer
43
+ const port = DEV_SERVER_CONFIG.PORT;
44
+ app.listen(port, () => {
45
+ console.log(`app listening on port ${port}!`)
46
+ })
47
+ }
48
+
@@ -0,0 +1 @@
1
+ module.exports = {};
@@ -0,0 +1,18 @@
1
+ const webpack = require('webpack');
2
+ const webpackConfig = require('./config/webpack.prod.js');
3
+
4
+ module.exports = () => {
5
+ console.log('\nbuilding for production...\n');
6
+
7
+ webpack(webpackConfig, (err, stats) => {
8
+ if (err) return console.log(err);
9
+ process.stdout.write(`${stats.toString({
10
+ colors: true, // 在控制台输出彩色信息
11
+ modules: false, // 不显示每个模块构建信息
12
+ children: false, // 不显示子编译任务信息
13
+ chunks: false, // 不显示每个代码块信息
14
+ chunkModules: true, // 显示每个代码块包含的模块信息
15
+ })}\n`)
16
+ })
17
+ }
18
+
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ name: 'lsw'
3
+ }
@@ -0,0 +1,20 @@
1
+ module.exports = (app) => {
2
+ return {
3
+ // 是否本地环境
4
+ isLocal () {
5
+ return process.env._ENV.trim() === 'local';
6
+ },
7
+ // 是否测试环境
8
+ isBeta () {
9
+ return process.env._ENV.trim() === 'beta';
10
+ },
11
+ // 是否生产环境
12
+ isProd() {
13
+ return process.env._ENV.trim() === 'prod';
14
+ },
15
+ // 获取当前环境
16
+ get() {
17
+ return process.env._ENV.trim() ?? 'local';
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,88 @@
1
+ const Koa = require('koa');
2
+ const path = require('path');
3
+ const { sep } = path; // 兼容不同操作系统的路径分隔符('/')
4
+ const env = require('./env');
5
+
6
+ // 引入loader
7
+ const configLoader = require('./loader/config');
8
+ const extendLoader = require('./loader/extend');
9
+ const serviceLoader = require('./loader/service');
10
+ const controllerLoader = require('./loader/controller');
11
+ const middlewareLoader = require('./loader/middleware');
12
+ const routerSchemaLoader = require('./loader/router-schema');
13
+ const routerLoader = require('./loader/router');
14
+
15
+ module.exports = {
16
+ /*
17
+ * 启动项目
18
+ * @params options 项目配置参数
19
+ * options = {
20
+ * name // 项目名称
21
+ * homePage // 项目首页
22
+ * }
23
+ */
24
+ start(options = {}) {
25
+ const app = new Koa();
26
+
27
+ // 应用配置
28
+ app.options = options;
29
+
30
+ // 基础路径
31
+ app.baseDir = process.cwd();
32
+
33
+ // 业务文件路径
34
+ app.businessPath = path.resolve(app.baseDir, `.${sep}app`);
35
+
36
+ // 初始化环境
37
+ app.env = env();
38
+ console.log(`当前环境:${app.env.get()}`)
39
+
40
+ // 加载config(其他模块可能依赖配置)
41
+ configLoader(app);
42
+ console.log(`--- [start] loader configLoader done ---`);
43
+ // 加载extend(可能需要在其他模块之前加载)
44
+ extendLoader(app);
45
+ console.log(`--- [start] loader extendLoader done ---`);
46
+ // 加载service(依赖config)
47
+ serviceLoader(app);
48
+ console.log(`--- [start] loader serviceLoader done ---`);
49
+ // 加载controller(依赖service)
50
+ controllerLoader(app);
51
+ console.log(`--- [start] loader controllerLoader done ---`);
52
+ // 加载middleware(依赖controller,因为中间件可能使用controller)
53
+ middlewareLoader(app);
54
+ console.log(`--- [start] loader middlewareLoader done ---`);
55
+ // 加载routerSchema(无依赖,但需要在路由注册前加载)
56
+ routerSchemaLoader(app);
57
+ console.log(`--- [start] loader routerSchemaLoader done ---`);
58
+
59
+ // 注册 elpis 全局中间件
60
+ const elpisMiddlwareePath = path.resolve(__dirname, `..${sep}app${sep}middleware.js`);
61
+ const elpisMiddleware = require(elpisMiddlwareePath);
62
+ elpisMiddleware(app);
63
+ console.log(`--- [start] loade global elpis middleware done ---`);
64
+
65
+
66
+ // 注册 业务 全局中间件
67
+ try {
68
+ require(`${app.businessPath}${sep}middleware.js`)(app);
69
+ console.log(`--- [start] loade global business middleware done ---`)
70
+ } catch(error) {
71
+ console.log('[exception] there is no global business middleware file');
72
+ }
73
+ // 注册路由
74
+ routerLoader(app);
75
+ console.log(`--- [start] loader routerLoader done ---`);
76
+
77
+ // 启动服务
78
+ try {
79
+ const port = process.env.PORT || 8080;
80
+ const host = process.env.HOST || '0.0.0.0';
81
+ app.listen(port, host)
82
+ console.log(`服务启动成功---port:${port}`)
83
+ } catch(erorr) {
84
+ console.error(`服务启动失败:${error}`)
85
+ }
86
+ return app;
87
+ }
88
+ }
@@ -0,0 +1,55 @@
1
+ const path = require('path');
2
+ const { sep } = path;
3
+ /*
4
+ * config loader
5
+ * @param {object} app koa 实例
6
+ * 配置区分 本地/测试/生产;通过 env 环境读取不同文件配置 env.config
7
+ * 通过 env.config 覆盖 default.config 加载到 app.config中
8
+ *
9
+ * 目录下对应的 config 配置:
10
+ * 默认配置 config/config.default.js
11
+ * 本地配置 config/config.local.js
12
+ * 测试配置 config/config.beta.js
13
+ * 生产配置 config/config.prod.js
14
+ */
15
+
16
+ module.exports = (app) => {
17
+ // elpis config 目录及相关文件
18
+ const elpisConfigPath = path.resolve(__dirname, `..${sep}..${sep}config`);
19
+ // 获取 default.config
20
+ let defaultConfig = require(path.resolve(elpisConfigPath, `.${sep}config.default.js`));
21
+
22
+ // 业务 config 目录及相关文件
23
+ const businessConfigPath = path.resolve(process.cwd(), `.${sep}config`)
24
+ try {
25
+ defaultConfig = {
26
+ ...defaultConfig,
27
+ ...require(path.resolve(businessConfigPath, `.${sep}config.default.js`))
28
+ };
29
+ } catch(e) {
30
+ if (e.code === 'MODULE_NOT_FOUND') {
31
+ console.log('[exception] config.default.js not found');
32
+ } else {
33
+ console.log('[exception] config.default.js load failed');
34
+ }
35
+ }
36
+ // 获取 env.config
37
+ let envConfig = {};
38
+ try {
39
+ if (app.env.isLocal()) {
40
+ envConfig = require(path.resolve(businessConfigPath, `.${sep}config.local.js`));
41
+ } else if (app.env.isBeta()) {
42
+ envConfig = require(path.resolve(businessConfigPath, `.${sep}config.beta.js`));
43
+ } else if (app.env.isProd()) {
44
+ envConfig = require(path.resolve(businessConfigPath, `.${sep}config.prod.js`));
45
+ }
46
+ } catch(e) {
47
+ if (e.code === 'MODULE_NOT_FOUND') {
48
+ console.log('[exception] there is no env.config file');
49
+ } else {
50
+ console.log('[exception] env.config load failed');
51
+ }
52
+ }
53
+ // 覆盖并加载 config 配置
54
+ app.config = Object.assign({}, defaultConfig, envConfig);
55
+ }
@@ -0,0 +1,56 @@
1
+ const glob = require('glob');
2
+ const path = require('path');
3
+ const { sep } = path;
4
+ /*
5
+ * controller loader
6
+ * @param {object} app Koa实例
7
+ *
8
+ * 加载所有controller,最终实现可通过”app.controller.${目录}.${文件}“访问
9
+ * 例子:
10
+ app/controller
11
+ |
12
+ | -- custom-module
13
+ |
14
+ | -- custom-controller.js
15
+ => app.controller.customModule.customController
16
+ *
17
+ */
18
+ module.exports = (app) => {
19
+ const controller = {};
20
+
21
+ // 读取 elpis/app/controller/**/**.js 下所有文件
22
+ const elpisControllerPath = path.resolve(__dirname, `..${sep}..${sep}app${sep}controller`);
23
+ const elpisFileList = glob.sync(path.resolve(elpisControllerPath, `.${sep}**${sep}**.js`));
24
+ elpisFileList.forEach(file => handleFile(file));
25
+
26
+ // 读取 业务/app/controller/**/**.js 下所有文件
27
+ const businessControllerPath = path.resolve(app.businessPath, `.${sep}controller`);
28
+ const businessFileList = glob.sync(path.resolve(businessControllerPath, `.${sep}**${sep}**.js`));
29
+ businessFileList.forEach(file => handleFile(file));
30
+
31
+ // 把内容加载到 app.controller下
32
+ function handleFile(file) {
33
+ // 提取文件名称
34
+ let name = path.resolve(file);
35
+ // 截取路径 app/controller/custom-module/custom-controller.js => custom-module/custom-controller
36
+ name = name.substring(name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length, name.lastIndexOf('.'));
37
+ // 把 '-' 统一改成驼峰式,custom-module/custom-controller.js => customModule.customController
38
+ name = name.replace(/[_-][a-z]/ig, (s) => s.substring(1).toUpperCase());
39
+ // 把controller挂在到内存 app 对象中
40
+ let tempController = controller;
41
+ const names = name.split(sep);
42
+ for (let i = 0, len = names.length; i < len; ++i) {
43
+ if (i === len - 1) {
44
+ const ControllerModule = require(path.resolve(file))(app);
45
+ tempController[names[i]] = new ControllerModule();
46
+ } else {
47
+ if (!tempController[names[i]]) {
48
+ tempController[names[i]] = {};
49
+ }
50
+ tempController = tempController[names[i]]
51
+ // 最终得到的数据格式 => controller为{ customModule: { customController: {} } }
52
+ }
53
+ }
54
+ };
55
+ app.controller = controller;
56
+ }