Skip to content
This repository was archived by the owner on Mar 11, 2021. It is now read-only.

Commit d235ed5

Browse files
bc022699bc022699
bc022699
authored and
bc022699
committed
Add Gulp task to auto-generate React components
1 parent 942e942 commit d235ed5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+18198
-1118
lines changed

.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "es2017", "stage-2"]
3+
}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ jspm_packages
3939
typings/
4040
dist/
4141
framework7-vue/
42+
framework7-react/
4243

4344
.DS_Store

.vscode/settings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"**/typings/": true,
77
"**/dist/": true,
88
"**/node_modules/": true,
9-
"**/framework7-vue/": true
9+
"**/framework7-vue/": true,
10+
"**/framework7-react/": true,
1011
},
1112
"typescript.tsdk": "./node_modules/typescript/lib"
1213
}

framework7-custom-build.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function getAllFw7ModulesAndDependenciesBeingUsed() {
6767
function getFw7CustomDependencies() {
6868
var fw7CustomDependencies = [];
6969

70-
for (i = 0; i < fw7ModulesBeingUsed.length; i++) {
70+
for (let i = 0; i < fw7ModulesBeingUsed.length; i++) {
7171
var module = fw7ModulesConfig[fw7ModulesBeingUsed[i]];
7272

7373
fw7CustomDependencies.push.apply(fw7CustomDependencies, module.dependencies);

framework7-react-component-gen.js

+319
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import {stringify} from 'json-fn';
2+
import {readFileSync, writeFileSync} from 'fs';
3+
import * as path from 'path';
4+
import * as fs from 'fs';
5+
import * as to from 'to-case';
6+
import * as babel from 'babel-core';
7+
8+
const ensureDirectoryExistence = (filePath) => {
9+
var dirname = path.dirname(filePath);
10+
11+
if (fs.existsSync(dirname)) {
12+
return true;
13+
}
14+
15+
ensureDirectoryExistence(dirname);
16+
17+
fs.mkdirSync(dirname);
18+
};
19+
20+
const generateImportString = (componentToImport, path) => {
21+
return `import {${componentToImport}} from '${path}';`
22+
};
23+
24+
const getPropType = (propValue) => {
25+
let propType = propValue.type || propValue;
26+
27+
if (Array.isArray(propType)) {
28+
return propType.reduce((typesArray, nextType) => {
29+
return [
30+
...typesArray,
31+
getPropType(nextType)
32+
]
33+
}, []).join(' | ');
34+
} else {
35+
switch(propType) {
36+
case Boolean:
37+
return 'boolean';
38+
case Number:
39+
return 'number';
40+
case String:
41+
return 'string';
42+
default:
43+
return 'string';
44+
}
45+
}
46+
};
47+
48+
const generateTypeScriptInterfaceFromProps = (props, componentName, eventList, slotList, mixin) => {
49+
let interfaceProps = [];
50+
51+
if (props) {
52+
interfaceProps = Object.keys(props).reduce((interfaceProps, nextPropName) => {
53+
const propType = getPropType(props[nextPropName]);
54+
55+
return [
56+
...interfaceProps,
57+
` ${to.camel(nextPropName)}?: ${propType};`
58+
];
59+
}, []);
60+
}
61+
62+
eventList.forEach(eventName => {
63+
interfaceProps.push(` ${to.camel('on-' + eventName.split(':').join('-'))}?: (eventArgs?: any) => void;`);
64+
});
65+
66+
slotList.forEach(slotName => {
67+
interfaceProps.push(` ${to.camel(slotName + '-slot')}?: React.ReactElement<any>;`)
68+
});
69+
70+
if (!props || !props.id) interfaceProps.push(' id?: string;');
71+
if (!props || !props.className) interfaceProps.push(' className?: string;');
72+
if (!props || !props.style) interfaceProps.push(' style?: string;');
73+
74+
if (interfaceProps.length) {
75+
if (mixin) {
76+
return `\nexport interface I${componentName}Props extends I${mixin}Props {\n${interfaceProps.join('\n')}\n}`;
77+
} else {
78+
return `\nexport interface I${componentName}Props {\n${interfaceProps.join('\n')}\n}`;
79+
}
80+
} else {
81+
return null;
82+
}
83+
};
84+
85+
const getEventList = (vueComponentString) => {
86+
const regex = new RegExp(/\$emit\(["']([A-Za-z0-9-:]+)["'],/, 'g');
87+
let match;
88+
const events = [];
89+
90+
while (match = regex.exec(vueComponentString)) {
91+
if (events.indexOf(match[1]) === -1) events.push(match[1]);
92+
}
93+
94+
return events;
95+
};
96+
97+
const getComponentToTagMappings = () => {
98+
const framework7Vue = readFileSync('./node_modules/framework7-vue/src/framework7-vue.js', 'utf8');
99+
const framework7VueString = stringify(framework7Vue);
100+
const regex = new RegExp(/["'](f7-[A-Za-z0-9-]+)["']\s*\:\s*([A-Za-z0-9]+),/, 'g');
101+
let match;
102+
const tagToComponentMap = {};
103+
const componentToTagMap = {};
104+
105+
while (match = regex.exec(framework7VueString)) {
106+
const tagName = match[1];
107+
const componentName = match[2];
108+
109+
tagToComponentMap[tagName] = componentName;
110+
componentToTagMap[componentName] = tagName;
111+
}
112+
113+
return {
114+
componentToTagMap,
115+
tagToComponentMap
116+
};
117+
};
118+
119+
const getInstantiatedComponentList = (vueComponentString, tagToComponentMap) => {
120+
return Object.keys(tagToComponentMap).reduce((componentList, nextTag) => {
121+
const regex = new RegExp(`["']${nextTag}["']`, 'g');
122+
123+
if (regex.test(vueComponentString) && componentList.indexOf(tagToComponentMap[nextTag]) === -1) {
124+
return componentList.concat([tagToComponentMap[nextTag]]);
125+
} else {
126+
return componentList;
127+
}
128+
}, []);
129+
};
130+
131+
const getSlotList = (vueComponentString) => {
132+
const regexDict = new RegExp(/\$slots\[\s*["']([A-Za-z0-9-:]+)["']\s*]/, 'g');
133+
const regexDot = new RegExp(/\$slots\.([A-Za-z0-9-:]+)/, 'g');
134+
const regexT = new RegExp(/_vm\._t\(["']([A-Za-z0-9-:]+)["']\)/, 'g');
135+
136+
let match;
137+
const slots = [];
138+
139+
[regexDict, regexDot, regexT].forEach(regex => {
140+
while (match = regex.exec(vueComponentString)) {
141+
if (slots.indexOf(match[1]) === -1 && match[1] !== 'default') slots.push(match[1]);
142+
}
143+
});
144+
145+
return slots;
146+
};
147+
148+
const getComponentMixinMap = () => {
149+
let framework7Vue = readFileSync('./framework7-vue/framework7-vue.js', 'utf8');
150+
const regex = new RegExp(/mixins:\s*\[([A-Za-z0-9]+)/, 'g');
151+
let match;
152+
153+
framework7Vue = framework7Vue.replace(regex, (mixinStatement, mixinName) => {
154+
return mixinStatement.replace(mixinName, `'${mixinName}'`);
155+
});
156+
157+
let transpiledFramework7Vue = babel.transform(framework7Vue, {
158+
presets: ['es2015']
159+
}).code;
160+
161+
const framework7VueExports = new Function('exports', transpiledFramework7Vue + '; return exports;')({});
162+
163+
return Object.keys(framework7VueExports).reduce((componentMixinMap, nextExportName) => {
164+
const exportedComponent = framework7VueExports[nextExportName];
165+
166+
if (exportedComponent.mixins) {
167+
return {
168+
...componentMixinMap,
169+
[nextExportName]: exportedComponent.mixins[0]
170+
};
171+
} else {
172+
return componentMixinMap;
173+
}
174+
}, {});
175+
};
176+
177+
const generateReactifyF7VueCall = (
178+
vueComponent,
179+
vueComponentName,
180+
vueComponentString,
181+
reactComponentName,
182+
componentToTagMappings,
183+
componentMixinMap,
184+
overrides
185+
) => {
186+
const reactifyF7VueArgs = [];
187+
let eventList;
188+
let instantiatedComponentList;
189+
let slotList;
190+
191+
const imports = [
192+
`import * as React from 'react'`,
193+
generateImportString('reactifyF7Vue', '../src/utils/reactifyF7Vue'),
194+
generateImportString(vueComponentName, '../framework7-vue/framework7-vue')
195+
];
196+
197+
reactifyF7VueArgs.push(`component: ${vueComponentName}`);
198+
reactifyF7VueArgs.push(`tag: '${componentToTagMappings.componentToTagMap[reactComponentName]}'`);
199+
200+
const componentOverrides = overrides && overrides[reactComponentName];
201+
202+
if (componentOverrides && componentOverrides.events) {
203+
eventList = componentOverrides.events;
204+
} else {
205+
eventList = getEventList(vueComponentString);
206+
}
207+
208+
if (eventList.length) reactifyF7VueArgs.push(`events: [\n\t\t'${eventList.join('\',\n\t\t\'')}'\n\t]`);
209+
210+
if (componentOverrides && componentOverrides.instantiatedComponents) {
211+
instantiatedComponentList = componentOverrides.instantiatedComponents.instantiatedComponents;
212+
} else {
213+
instantiatedComponentList = getInstantiatedComponentList(vueComponentString, componentToTagMappings.tagToComponentMap);
214+
}
215+
216+
if (instantiatedComponentList.length) reactifyF7VueArgs.push(`instantiatedComponents: [\n\t\t${instantiatedComponentList.join(',\n\t\t')}\n\t]`);
217+
218+
if (componentOverrides && componentOverrides.slots) {
219+
slotList = componentOverrides.slots;
220+
} else {
221+
slotList = getSlotList(vueComponentString);
222+
}
223+
224+
if (slotList.length) reactifyF7VueArgs.push(`slots: [\n\t\t'${slotList.join('\',\n\t\t\'')}'\n\t]`);
225+
226+
imports.push(...instantiatedComponentList.map(componentName => {
227+
return generateImportString(componentName, `./${componentName}`)
228+
}));
229+
230+
const mixin = componentMixinMap[vueComponentName];
231+
232+
if (mixin) {
233+
imports.push(generateImportString(`Vue${mixin}`, '../framework7-vue/framework7-vue'));
234+
imports.push(generateImportString(`I${mixin}Props`, `./${mixin}`));
235+
reactifyF7VueArgs.push(`mixin: Vue${mixin}`);
236+
}
237+
238+
const typeScriptInterface = generateTypeScriptInterfaceFromProps(
239+
vueComponent.props,
240+
reactComponentName,
241+
eventList,
242+
slotList,
243+
mixin
244+
);
245+
246+
const propInterfaceName = (typeScriptInterface) ? `I${reactComponentName}Props` : 'void';
247+
248+
const reactifyVueCall = `\nexport const ${reactComponentName} = reactifyF7Vue<${propInterfaceName}>({\n\t${reactifyF7VueArgs.join(',\n\t')}\n});`;
249+
250+
const componentCode = [
251+
imports.join('\n'),
252+
typeScriptInterface
253+
];
254+
255+
if (vueComponentName.indexOf('Mixin') === -1) componentCode.push(reactifyVueCall);
256+
257+
return componentCode.join('\n');
258+
};
259+
260+
const generateIndexTsFile = (vueComponents, excludes) => {
261+
const importedFiles = [];
262+
const exportedModules = [];
263+
264+
importedFiles.push(generateImportString('Framework7App', '../src/components/Framework7App'));
265+
exportedModules.push('Framework7App');
266+
267+
Object.keys(vueComponents).forEach(vueComponentName => {
268+
if (vueComponentName.indexOf('Mixin') === -1) {
269+
const reactComponentName = vueComponentName.replace('Vue', '');
270+
271+
if (excludes && excludes.indexOf(reactComponentName) !== -1) {
272+
importedFiles.push(generateImportString(reactComponentName, `../src/components/${reactComponentName}`));
273+
} else {
274+
importedFiles.push(generateImportString(reactComponentName, `./${reactComponentName}`));
275+
}
276+
277+
exportedModules.push(reactComponentName);
278+
}
279+
});
280+
281+
const indexTsFile = `${importedFiles.join('\n')}\n\nexport {\n\t${exportedModules.join(',\n\t')}\n}`;
282+
const outPath = './framework7-react/index.ts';
283+
284+
writeFileSync(outPath, indexTsFile);
285+
}
286+
287+
export const generateReactComponents = (args) => {
288+
const componentFile = [];
289+
290+
const componentMixinMap = getComponentMixinMap();
291+
292+
const vueComponents = require('./framework7-vue/framework7-vue');
293+
294+
Object.keys(vueComponents).forEach(vueComponentName => {
295+
const reactComponentName = vueComponentName.replace('Vue', '');
296+
const vueComponent = vueComponents[vueComponentName]
297+
const vueComponentString = stringify(vueComponent).split(`\\"`).join('"');
298+
const componentToTagMappings = getComponentToTagMappings();
299+
300+
if (!args || !args.exclude || args.exclude.indexOf(reactComponentName) === -1) {
301+
const reactifyF7VueCall = generateReactifyF7VueCall(
302+
vueComponent,
303+
vueComponentName,
304+
vueComponentString,
305+
reactComponentName,
306+
componentToTagMappings,
307+
componentMixinMap,
308+
args && args.overrides
309+
);
310+
311+
const outPath = `./framework7-react/${reactComponentName}.ts`;
312+
313+
ensureDirectoryExistence(outPath);
314+
writeFileSync(outPath, reactifyF7VueCall);
315+
}
316+
});
317+
318+
generateIndexTsFile(vueComponents, args && args.exclude);
319+
};

0 commit comments

Comments
 (0)