@cuipengyu5/build-plugin-lowcode 0.0.1 → 0.0.2

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.
@@ -0,0 +1,252 @@
1
+ const _ = require('lodash');
2
+ const path = require('path');
3
+ const fse = require('fs-extra');
4
+ const hbs = require('handlebars');
5
+ const glob = require('glob');
6
+ const parseProps = require('./parse-props');
7
+
8
+ /**
9
+ * @description generate js file as webpack entry
10
+ * @param {String} template template path
11
+ * @param {String} filename
12
+ * @param {String} rootDir
13
+ * @param {Object} params params for compile template content
14
+ * @returns {String} path of entry file
15
+ */
16
+ function generateEntry({ template, filename = 'index.js', rootDir = process.cwd(), params, tmpDirName = '.tmp' }) {
17
+ const hbsTemplatePath = path.join(__dirname, `../templates/${template}`);
18
+ const hbsTemplateContent = fse.readFileSync(hbsTemplatePath, 'utf-8');
19
+ const compileTemplateContent = hbs.compile(hbsTemplateContent);
20
+
21
+ const tempDir = path.join(rootDir, tmpDirName);
22
+ const jsPath = path.join(tempDir, filename);
23
+
24
+ const jsTemplateContent = compileTemplateContent(params);
25
+ fse.outputFileSync(jsPath, jsTemplateContent);
26
+
27
+ return jsPath;
28
+ }
29
+
30
+ function parseNpmName(npmName) {
31
+ if (typeof npmName !== 'string') {
32
+ throw new TypeError('Expected a string');
33
+ }
34
+ const matched =
35
+ npmName.charAt(0) === '@' ? /(@[^\/]+)\/(.+)/g.exec(npmName) : [npmName, '', npmName];
36
+ if (!matched) {
37
+ throw new Error(`[parse-package-name] "${npmName}" is not a valid string`);
38
+ }
39
+ const scope = matched[1];
40
+ const name = (matched[2] || '').replace(/\s+/g, '').replace(/[\-_]+([^\-_])/g, ($0, $1) => {
41
+ return $1.toUpperCase();
42
+ });
43
+ const uniqueName =
44
+ (matched[1]
45
+ ? matched[1].charAt(1).toUpperCase() +
46
+ matched[1].slice(2).replace(/[\-_]+([^\-_])/g, ($0, $1) => {
47
+ return $1.toUpperCase();
48
+ })
49
+ : '') +
50
+ name.charAt(0).toUpperCase() +
51
+ name.slice(1);
52
+ return {
53
+ scope,
54
+ name,
55
+ uniqueName,
56
+ };
57
+ }
58
+
59
+ function camel2KebabComponentName(camel) {
60
+ return camel
61
+ .replace(/[A-Z]/g, (item) => {
62
+ return `-${item.toLowerCase()}`;
63
+ })
64
+ .replace(/^\-/, '');
65
+ }
66
+
67
+ function kebab2CamelComponentName(kebab) {
68
+ const camel = kebab.charAt(0).toUpperCase() + kebab.substr(1);
69
+ return camel.replace(/-([a-z])/g, (keb, item) => {
70
+ return item.toUpperCase();
71
+ });
72
+ }
73
+
74
+ function generateComponentList(components) {
75
+ const componentList = [
76
+ {
77
+ title: '常用',
78
+ icon: '',
79
+ children: [],
80
+ },
81
+ {
82
+ title: '容器',
83
+ icon: '',
84
+ children: [],
85
+ },
86
+ {
87
+ title: '导航',
88
+ icon: '',
89
+ children: [],
90
+ },
91
+ {
92
+ title: '内容',
93
+ icon: '',
94
+ children: [],
95
+ },
96
+ {
97
+ title: 'Feedback 反馈',
98
+ icon: '',
99
+ children: [],
100
+ },
101
+ ];
102
+
103
+ components.forEach((comp) => {
104
+ const category = comp.category || '其他';
105
+ let target = componentList.find((item) => item.title === category);
106
+ if (!target) {
107
+ target = {
108
+ title: category,
109
+ icon: '',
110
+ children: [],
111
+ };
112
+
113
+ componentList.push(target);
114
+ }
115
+
116
+ if (comp.snippets) {
117
+ target.children.push({
118
+ componentName: comp.componentName,
119
+ title: comp.title || comp.componentName,
120
+ icon: '',
121
+ package: comp.npm.pkg,
122
+ snippets: comp.snippets || [],
123
+ });
124
+ }
125
+ });
126
+ return componentList;
127
+ }
128
+
129
+ function replacer(key, value) {
130
+ if (typeof value === 'function') {
131
+ return {
132
+ type: 'JSFunction',
133
+ value: String(value),
134
+ };
135
+ }
136
+ return value;
137
+ }
138
+
139
+ function isAsyncFunction(fn) {
140
+ return fn[Symbol.toStringTag] === 'AsyncFunction';
141
+ }
142
+ function reviewer(key, value) {
143
+ if (!value) {
144
+ return value;
145
+ }
146
+ if (key === 'icon') {
147
+ if (typeof value === 'object') {
148
+ return {
149
+ type: 'smile',
150
+ size: 'small',
151
+ };
152
+ }
153
+ }
154
+ if (typeof value === 'object') {
155
+ if (value.type === 'JSFunction') {
156
+ let _value = value.value && value.value.trim();
157
+ let template = `
158
+ return function lowcode() {
159
+ const self = this;
160
+ try {
161
+ return (${_value}).apply(self, arguments);
162
+ } catch(e) {
163
+ console.log('call function which parsed by lowcode for key ${key} failed: ', e);
164
+ return e.message;
165
+ }
166
+ };`;
167
+ try {
168
+ return Function(template)();
169
+ } catch (e) {
170
+ if (e && e.message.includes("Unexpected token '{'")) {
171
+ console.log('method need add funtion prefix');
172
+ _value = `function ${_value}`;
173
+ template = `
174
+ return function lowcode() {
175
+ const self = this;
176
+ try {
177
+ return (${_value}).apply(self, arguments);
178
+ } catch(e) {
179
+ console.log('call function which parsed by lowcode for key ${key} failed: ', e);
180
+ return e.message;
181
+ }
182
+ };`;
183
+ return Function(template)();
184
+ }
185
+ console.error('parse lowcode function error: ', e);
186
+ console.error(value);
187
+ return value;
188
+ }
189
+ }
190
+ }
191
+ return value;
192
+ }
193
+
194
+ function toJson(object, replacer) {
195
+ return JSON.stringify(object, replacer || this.replacer, 2);
196
+ }
197
+
198
+ function parseJson(json) {
199
+ const input = typeof json === 'string' ? json : JSON.stringify(json);
200
+ return JSON.parse(input, this.reviewer);
201
+ }
202
+
203
+ function asyncDebounce(func, wait) {
204
+ const debounced = _.debounce(async (resolve, reject, bindSelf, args) => {
205
+ try {
206
+ const result = await func.bind(bindSelf)(...args);
207
+ resolve(result);
208
+ } catch (error) {
209
+ reject(error);
210
+ }
211
+ }, wait);
212
+
213
+ // This is the function that will be bound by the caller, so it must contain the `function` keyword.
214
+ function returnFunc(...args) {
215
+ return new Promise((resolve, reject) => {
216
+ debounced(resolve, reject, this, args);
217
+ });
218
+ }
219
+
220
+ return returnFunc;
221
+ }
222
+
223
+ function getUsedComponentMetas(rootDir, lowcodeDir = 'lowcode', metaFilename, components) {
224
+ let metaPaths = glob.sync(
225
+ path.resolve(rootDir, `${lowcodeDir}/**/${metaFilename}.@(js|ts|jsx|tsx)`),
226
+ );
227
+ if (metaPaths && metaPaths.length) {
228
+ metaPaths = metaPaths.map((item) => {
229
+ return item.slice(
230
+ path.resolve(rootDir, lowcodeDir).length + 1,
231
+ item.lastIndexOf(metaFilename) - 1,
232
+ );
233
+ });
234
+ }
235
+ return components
236
+ ? components.filter((component) => {
237
+ return metaPaths.includes(camel2KebabComponentName(component));
238
+ })
239
+ : metaPaths.map((dir) => kebab2CamelComponentName(dir));
240
+ }
241
+
242
+ module.exports = {
243
+ toJson,
244
+ parseProps,
245
+ parseNpmName,
246
+ generateEntry,
247
+ asyncDebounce,
248
+ generateComponentList,
249
+ camel2KebabComponentName,
250
+ kebab2CamelComponentName,
251
+ getUsedComponentMetas,
252
+ };
@@ -0,0 +1,41 @@
1
+ const path = require('path');
2
+ const { pathExists } = require('fs-extra');
3
+ const spawn = require('cross-spawn-promise');
4
+
5
+ async function isNPMInstalled(args) {
6
+ return pathExists(path.join(args.workDir, 'node_modules'));
7
+ }
8
+
9
+ async function install(args) {
10
+ if (await isNPMInstalled(args)) return;
11
+ const { workDir, npmClient = 'tnpm' } = args;
12
+ try {
13
+ await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: workDir });
14
+ } catch (e) {
15
+ // TODO
16
+ }
17
+ }
18
+
19
+ async function isNPMModuleInstalled(args, name) {
20
+ const modulePkgJsonPath = path.resolve(args.workDir || '', 'node_modules', name, 'package.json');
21
+ return pathExists(modulePkgJsonPath);
22
+ }
23
+
24
+ async function installModule(args, name) {
25
+ if (await isNPMModuleInstalled(args, name)) return;
26
+ const { workDir, npmClient = 'tnpm' } = args;
27
+ try {
28
+ await spawn(npmClient, [
29
+ 'i',
30
+ name,
31
+ npmClient === 'npm' ? '--registry=https://registry.npmmirror.com' : ''
32
+ ], { stdio: 'inherit', cwd: workDir });
33
+ } catch (e) {
34
+ // TODO
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ installModule,
40
+ install,
41
+ };
@@ -0,0 +1,261 @@
1
+ function propConfigToFieldConfig(propConfig) {
2
+ const { name, description } = propConfig;
3
+ const title = {
4
+ label: {
5
+ type: 'i18n',
6
+ 'en-US': name,
7
+ 'zh-CN': (description && description.slice(0, 10)) || name,
8
+ },
9
+ tip: description ? `${name} | ${description}` : undefined,
10
+ };
11
+ const setter = propConfig.setter ? propConfig.setter : propTypeToSetter(propConfig.propType);
12
+ delete propConfig.propType;
13
+ return {
14
+ title,
15
+ ...propConfig,
16
+ // TODO 这边直接用propConfig,将setter丢在propconfig里,需要确认是否在PropConfig扩展还是换实现
17
+ setter,
18
+ };
19
+ }
20
+
21
+ function propTypeToSetter(propType) {
22
+ let typeName;
23
+ let isRequired = false;
24
+ if (typeof propType === 'string') {
25
+ typeName = propType;
26
+ } else if (typeof propType === 'object') {
27
+ typeName = propType.type;
28
+ isRequired = propType.isRequired;
29
+ } else {
30
+ typeName = 'string';
31
+ }
32
+ // TODO: use mixinSetter wrapper
33
+ switch (typeName) {
34
+ case 'string':
35
+ return {
36
+ componentName: 'StringSetter',
37
+ isRequired,
38
+ initialValue: '',
39
+ };
40
+ case 'number':
41
+ return {
42
+ componentName: 'NumberSetter',
43
+ isRequired,
44
+ initialValue: 0,
45
+ };
46
+ case 'bool':
47
+ return {
48
+ componentName: 'BoolSetter',
49
+ isRequired,
50
+ initialValue: false,
51
+ };
52
+ case 'oneOf':
53
+ const dataSource = (propType.value || []).map((value, index) => {
54
+ const t = typeof value;
55
+ return {
56
+ label:
57
+ t === 'string' || t === 'number' || t === 'boolean' ? String(value) : `value ${index}`,
58
+ value,
59
+ };
60
+ });
61
+ const componentName = dataSource.length >= 4 ? 'SelectSetter' : 'RadioGroupSetter';
62
+ return {
63
+ componentName,
64
+ props: { dataSource, options: dataSource },
65
+ isRequired,
66
+ initialValue: dataSource[0] ? dataSource[0].value : null,
67
+ };
68
+
69
+ case 'element':
70
+ case 'node': // TODO: use Mixin
71
+ return {
72
+ // slotSetter
73
+ componentName: 'SlotSetter',
74
+ props: {
75
+ mode: typeName,
76
+ },
77
+ isRequired,
78
+ initialValue: {
79
+ type: 'JSSlot',
80
+ value: [],
81
+ },
82
+ };
83
+ case 'shape':
84
+ case 'exact':
85
+ const items = (propType.value || []).map((item) => propConfigToFieldConfig(item));
86
+ return {
87
+ componentName: 'ObjectSetter',
88
+ props: {
89
+ config: {
90
+ items,
91
+ extraSetter: typeName === 'shape' ? propTypeToSetter('any') : null,
92
+ },
93
+ },
94
+ isRequired,
95
+ initialValue: (field) => {
96
+ const data = {};
97
+ items.forEach((item) => {
98
+ let initial = item.defaultValue;
99
+ if (initial == null && item.setter && typeof item.setter === 'object') {
100
+ initial = item.setter.initialValue;
101
+ }
102
+ data[item.name] = initial
103
+ ? typeof initial === 'function'
104
+ ? initial(field)
105
+ : initial
106
+ : null;
107
+ });
108
+ return data;
109
+ },
110
+ };
111
+ case 'object':
112
+ case 'objectOf':
113
+ return {
114
+ componentName: 'ObjectSetter',
115
+ props: {
116
+ config: {
117
+ extraSetter: propTypeToSetter(typeName === 'objectOf' ? propType.value : 'any'),
118
+ },
119
+ },
120
+ isRequired,
121
+ initialValue: {},
122
+ };
123
+ case 'array':
124
+ case 'arrayOf':
125
+ return {
126
+ componentName: 'ArraySetter',
127
+ props: {
128
+ itemSetter: propTypeToSetter(typeName === 'arrayOf' ? propType.value : 'any'),
129
+ },
130
+ isRequired,
131
+ initialValue: [],
132
+ };
133
+ case 'func':
134
+ return {
135
+ componentName: 'FunctionSetter',
136
+ isRequired,
137
+ };
138
+ case 'color':
139
+ return {
140
+ componentName: 'ColorSetter',
141
+ isRequired,
142
+ };
143
+ case 'oneOfType':
144
+ return {
145
+ componentName: 'MixedSetter',
146
+ props: {
147
+ // TODO:
148
+ setters: (propType.value || []).map((item) => propTypeToSetter(item)),
149
+ },
150
+ isRequired,
151
+ };
152
+ default:
153
+ // do nothing
154
+ }
155
+ return {
156
+ componentName: 'MixedSetter',
157
+ isRequired,
158
+ props: {},
159
+ };
160
+ }
161
+
162
+ const EVENT_RE = /^on|after|before[A-Z][\w]*$/;
163
+
164
+ module.exports = function (metadata) {
165
+ const { configure = {} } = metadata;
166
+ // TODO types后续补充
167
+ let extendsProps = null;
168
+ if (configure.props) {
169
+ if (Array.isArray(configure.props)) {
170
+ return metadata;
171
+ }
172
+ const { isExtends, override = [] } = configure.props;
173
+ // 不开启继承时,直接返回configure配置
174
+ if (!isExtends) {
175
+ return {
176
+ ...metadata,
177
+ configure: {
178
+ ...configure,
179
+ props: [...override],
180
+ },
181
+ };
182
+ }
183
+
184
+ extendsProps = {};
185
+ // 开启继承后,缓存重写内容的配置
186
+ override.forEach((prop) => {
187
+ extendsProps[prop.name] = prop;
188
+ });
189
+ }
190
+
191
+ if (!metadata.props) {
192
+ return {
193
+ ...metadata,
194
+ configure: {
195
+ ...configure,
196
+ props: [],
197
+ },
198
+ };
199
+ }
200
+ const { component = {}, supports = {} } = configure;
201
+ const supportedEvents = supports.events ? null : [];
202
+ const props = [];
203
+
204
+ metadata.props.forEach((prop) => {
205
+ const { name, propType, description } = prop;
206
+ if (
207
+ name === 'children' &&
208
+ (component.isContainer || propType === 'node' || propType === 'element' || propType === 'any')
209
+ ) {
210
+ if (component.isContainer !== false) {
211
+ component.isContainer = true;
212
+ props.push(propConfigToFieldConfig(prop));
213
+ return;
214
+ }
215
+ }
216
+
217
+ if (EVENT_RE.test(name) && (propType === 'func' || propType === 'any')) {
218
+ if (supportedEvents) {
219
+ supportedEvents.push({
220
+ name,
221
+ description,
222
+ });
223
+ supports.events = supportedEvents;
224
+ }
225
+ return;
226
+ }
227
+
228
+ if (name === 'className' && (propType === 'string' || propType === 'any')) {
229
+ if (supports.className == null) {
230
+ supports.className = true;
231
+ }
232
+ return;
233
+ }
234
+
235
+ if (name === 'style' && (propType === 'object' || propType === 'any')) {
236
+ if (supports.style == null) {
237
+ supports.style = true;
238
+ }
239
+ return;
240
+ }
241
+
242
+ // 存在覆盖配置时
243
+ if (extendsProps) {
244
+ if (name in extendsProps) {
245
+ prop = extendsProps[name];
246
+ }
247
+ }
248
+
249
+ props.push(propConfigToFieldConfig(prop));
250
+ });
251
+
252
+ return {
253
+ ...metadata,
254
+ configure: {
255
+ ...configure,
256
+ props,
257
+ supports,
258
+ component,
259
+ },
260
+ };
261
+ };
@@ -1,5 +0,0 @@
1
-
2
- import Button from './components/button';
3
-
4
- export const bizCssPrefix = 'bizpack';
5
- export { Button };
@@ -1,6 +0,0 @@
1
-
2
- import Button from './components/button';
3
- import Input from './components/input';
4
-
5
- export const bizCssPrefix = 'bizpack';
6
- export { Button, Input };