feat: support localizing

This commit is contained in:
gengyang 2020-03-31 20:20:20 +08:00
parent 770a1b6c34
commit e1faa8452c
15 changed files with 8864 additions and 228 deletions

View File

@ -20,6 +20,7 @@
"jest-watch-typeahead": "^0.3.1", "jest-watch-typeahead": "^0.3.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"json-schema-to-typescript": "^8.2.0", "json-schema-to-typescript": "^8.2.0",
"tslib": "^1.11.1",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },
"scripts": { "scripts": {

View File

@ -46,7 +46,7 @@ export async function genManifest(
package: matScanModel.pkgName, package: matScanModel.pkgName,
version: matScanModel.pkgVersion, version: matScanModel.pkgVersion,
exportName: matParsedModel.componentName, exportName: matParsedModel.componentName,
main: matScanModel.mainEntry, main: matScanModel.entryFilePath,
destructuring: false, destructuring: false,
subName: '', subName: '',
}, },

View File

@ -10,17 +10,17 @@ import generate from './generate';
import parse from './parse'; import parse from './parse';
import localize from './localize'; import localize from './localize';
export default async function( export default async function(options: IMaterializeOptions): Promise<ComponentMeta[]> {
options: IMaterializeOptions,
): Promise<ComponentMeta[]> {
const { accesser = 'local' } = options; const { accesser = 'local' } = options;
if (accesser === 'online') { if (accesser === 'online') {
const { entry, cwd } = await localize(options); const entry = await localize(options);
options.entry = entry; options.entry = entry;
options.cwd = cwd;
} }
const scanedModel = await scan(options); const scanedModel = await scan(options);
const parsedModel = await parse(scanedModel.modules[0]); const parsedModel = await parse({
filePath: scanedModel.entryFilePath,
fileContent: scanedModel.entryFileContent,
});
const result = await generate(scanedModel, parsedModel); const result = await generate(scanedModel, parsedModel);
return result; return result;
} }

View File

@ -33,13 +33,13 @@ export async function createFakePackage(params: {
pkgJsonFilePath, pkgJsonFilePath,
JSON.stringify({ JSON.stringify({
name: params.pkgName, name: params.pkgName,
version: params.pkgVersion, version: params.pkgVersion || '0.0.0',
dependencies: { dependencies: {
[params.pkgName]: params.pkgVersion, [params.pkgName]: params.pkgVersion || 'latest',
}, },
}), }),
); );
debugger;
// 安装依赖 // 安装依赖
const npmClient = params.npmClient || 'tnpm'; const npmClient = params.npmClient || 'tnpm';
await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: tempDir } as any); await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: tempDir } as any);
@ -68,12 +68,12 @@ export async function createTempDir(): Promise<string> {
* @returns {{ [key: string]: any }} * @returns {{ [key: string]: any }}
* @memberof OnlineAccesser * @memberof OnlineAccesser
*/ */
export function getPkgNameAndVersion( export function getPkgNameAndVersion(pkgNameWithVersion: string): { [key: string]: any } {
pkgNameWithVersion: string,
): { [key: string]: any } {
const matches = pkgNameWithVersion.match(/(@\d+\.\d+\.\d+)$/); const matches = pkgNameWithVersion.match(/(@\d+\.\d+\.\d+)$/);
if (!matches) { if (!matches) {
throw new OtterError(`Illegal semver version: ${pkgNameWithVersion}`); return {
name: pkgNameWithVersion,
};
} }
const semverObj = semver.coerce(matches[0]); const semverObj = semver.coerce(matches[0]);
const name = pkgNameWithVersion.replace(matches[0], ''); const name = pkgNameWithVersion.replace(matches[0], '');
@ -84,12 +84,7 @@ export function getPkgNameAndVersion(
} }
// 将问题转化为本地物料化场景 // 将问题转化为本地物料化场景
export default async function localize( export default async function localize(options: IMaterializeOptions): Promise<string> {
options: IMaterializeOptions,
): Promise<{
cwd: string;
entry: string;
}> {
// 创建临时目录 // 创建临时目录
const tempDir = await createTempDir(); const tempDir = await createTempDir();
// 创建组件包 // 创建组件包
@ -101,8 +96,5 @@ export default async function localize(
npmClient: options.npmClient, npmClient: options.npmClient,
}); });
return { return join(tempDir, 'node_modules', name);
cwd: tempDir,
entry: join(tempDir, 'node_modules', name),
};
} }

View File

@ -5,10 +5,7 @@ import { IMaterialParsedModel, IMaterialScanModel } from '../types';
import resolver from './resolver'; import resolver from './resolver';
import handlers from './handlers'; import handlers from './handlers';
export default function parse(params: { export default function parse(params: { fileContent: string; filePath: string }): Promise<IMaterialParsedModel[]> {
fileContent: string;
filePath: string;
}): Promise<IMaterialParsedModel[]> {
const { fileContent, filePath } = params; const { fileContent, filePath } = params;
const result = reactDocs.parse( const result = reactDocs.parse(
fileContent, fileContent,

View File

@ -49,10 +49,7 @@ export function transformType(type: any) {
} = type; } = type;
if (properties.length === 0) { if (properties.length === 0) {
result.type = 'object'; result.type = 'object';
} else if ( } else if (properties.length === 1 && typeof properties[0].key === 'object') {
properties.length === 1 &&
typeof properties[0].key === 'object'
) {
result.type = 'objectOf'; result.type = 'objectOf';
const v = transformType(properties[0].value); const v = transformType(properties[0].value);
if (typeof v.type === 'string') result.value = v.type; if (typeof v.type === 'string') result.value = v.type;
@ -81,7 +78,7 @@ export function transformType(type: any) {
break; break;
case 'exact': case 'exact':
case 'shape': case 'shape':
result.value = Object.keys(value).map(n => { result.value = Object.keys(value).map((n) => {
// tslint:disable-next-line:variable-name // tslint:disable-next-line:variable-name
const { name: _name, ...others } = value[n]; const { name: _name, ...others } = value[n];
return transformItem(n, { return transformItem(n, {
@ -110,13 +107,7 @@ export function transformType(type: any) {
} }
export function transformItem(name: string, item: any) { export function transformItem(name: string, item: any) {
const { const { description, flowType, type = flowType, required, defaultValue } = item;
description,
flowType,
type = flowType,
required,
defaultValue,
} = item;
const result: any = { const result: any = {
name, name,
propType: transformType({ propType: transformType({
@ -133,6 +124,9 @@ export function transformItem(name: string, item: any) {
result.defaultValue = value; result.defaultValue = value;
} catch (e) {} } catch (e) {}
} }
if (result.propType === undefined) {
delete result.propType;
}
return result; return result;
} }

View File

@ -1,26 +1,25 @@
import { IMaterializeOptions, IMaterialScanModel, SourceType } from './types'; import { IMaterializeOptions, IMaterialScanModel, SourceType } from './types';
import { pathExists, readFile } from 'fs-extra'; import { pathExists, readFile, lstatSync } from 'fs-extra';
import { join } from 'path'; import { join } from 'path';
import { debug } from './otter-core'; import { debug } from './otter-core';
const log = debug.extend('mat'); const log = debug.extend('mat');
export default async function scan( export default async function scan(options: IMaterializeOptions): Promise<IMaterialScanModel> {
options: IMaterializeOptions,
): Promise<IMaterialScanModel> {
const model: IMaterialScanModel = { const model: IMaterialScanModel = {
pkgName: '', pkgName: '',
pkgVersion: '', pkgVersion: '',
mainEntry: '',
sourceType: SourceType.MODULE, sourceType: SourceType.MODULE,
modules: [], entryFilePath: '',
entryFileContent: '',
}; };
log('options', options); log('options', options);
// 入口文件路径 // 入口文件路径
let entryFilePath = null; let entryFilePath = options.entry;
const cwd = options.cwd ? options.cwd : ''; const stats = lstatSync(entryFilePath);
const entry = options.entry; debugger;
const isDepsMode = cwd !== entry; if (!stats.isFile()) {
const pkgJsonPath = join(cwd, 'package.json'); let mainFilePath = '';
const pkgJsonPath = join(entryFilePath, 'package.json');
// 判断是否存在 package.json // 判断是否存在 package.json
if (!(await pathExists(pkgJsonPath))) { if (!(await pathExists(pkgJsonPath))) {
throw new Error(`Cannot find package.json. ${pkgJsonPath}`); throw new Error(`Cannot find package.json. ${pkgJsonPath}`);
@ -29,30 +28,26 @@ export default async function scan(
let pkgJson = await resolvePkgJson(pkgJsonPath); let pkgJson = await resolvePkgJson(pkgJsonPath);
model.pkgName = pkgJson.name; model.pkgName = pkgJson.name;
model.pkgVersion = pkgJson.version; model.pkgVersion = pkgJson.version;
if (isDepsMode) {
pkgJson = await resolvePkgJson(join(entry, 'package.json'));
}
if (pkgJson.module) { if (pkgJson.module) {
// 支持 es module // 支持 es module
model.sourceType = SourceType.MODULE; model.sourceType = SourceType.MODULE;
entryFilePath = pkgJson.module; mainFilePath = pkgJson.module;
} else if (pkgJson.main) { } else if (pkgJson.main) {
// 支持 commonjs // 支持 commonjs
model.sourceType = SourceType.MAIN; model.sourceType = SourceType.MAIN;
entryFilePath = pkgJson.main; mainFilePath = pkgJson.main;
} else { } else {
entryFilePath = './index.js'; mainFilePath = './index.js';
} }
entryFilePath = join(isDepsMode ? entry : cwd, entryFilePath); entryFilePath = join(entryFilePath, mainFilePath);
}
log('entryFilePath', entryFilePath); log('entryFilePath', entryFilePath);
const entryFile = await loadFile(entryFilePath); const entryFileContent = await loadFile(entryFilePath);
log('entryFile', entryFile); log('entryFile', entryFileContent);
model.mainEntry = entryFilePath; model.entryFilePath = entryFilePath;
model.entryFileContent = entryFileContent;
// 记录入口文件 // 记录入口文件
model.modules.push({
filePath: entryFilePath,
fileContent: entryFile,
});
log('model', model); log('model', model);
return model; return model;
} }
@ -65,9 +60,7 @@ export async function loadFile(filePath: string): Promise<string> {
return content.toString(); return content.toString();
} }
export async function resolvePkgJson( export async function resolvePkgJson(pkgJsonPath: string): Promise<{ [k: string]: any }> {
pkgJsonPath: string,
): Promise<{ [k: string]: any }> {
const content = await loadFile(pkgJsonPath); const content = await loadFile(pkgJsonPath);
const json = JSON.parse(content); const json = JSON.parse(content);
return json; return json;

View File

@ -2,15 +2,12 @@
* *
*/ */
interface IMaterialScanModel { interface IMaterialScanModel {
/** 入口文件地址 */
mainEntry: string;
/** 标记物料组件包所使用的模块规范 */ /** 标记物料组件包所使用的模块规范 */
sourceType: 'module' | 'main'; sourceType: 'module' | 'main';
/** 每个文件对应的文件内容 */ /** 入口文件路径 */
modules: { entryFilePath: string;
filePath: string; /** 入口文件内容 */
fileContent: string; entryFileContent: string;
}[];
/** 当前包名 */ /** 当前包名 */
pkgName: string; pkgName: string;
/** 当前包版本 */ /** 当前包版本 */

View File

@ -7,7 +7,7 @@ import IExtensionConfigManifest from './IExtensionConfigManifest';
*/ */
interface IMaterializeOptions { interface IMaterializeOptions {
/** /**
* * ()
* *
* /usr/project/src/container/DemoMaterial * /usr/project/src/container/DemoMaterial
* @ali/demo-material@0.0.1 * @ali/demo-material@0.0.1
@ -22,13 +22,6 @@ interface IMaterializeOptions {
*/ */
accesser: 'local' | 'online'; accesser: 'local' | 'online';
/**
* accesser=local /usr/.../demo-project
* @type {string}
* @memberof IMaterializeOptions
*/
cwd?: string;
/** /**
* *
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,7 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1 > Snapshot 1
{ {
mainEntry: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', entryFileContent: `import AIMakeBlank from './basic/AIMakeBlank';␊
modules: [
{
fileContent: `import AIMakeBlank from './basic/AIMakeBlank';␊
import AIMakeIcon from './basic/AIMakeIcon';␊ import AIMakeIcon from './basic/AIMakeIcon';␊
import AIMakeImage from './basic/AIMakeImage';␊ import AIMakeImage from './basic/AIMakeImage';␊
import AIMakeLink from './basic/AIMakeLink';␊ import AIMakeLink from './basic/AIMakeLink';␊
@ -22,9 +19,7 @@ Generated by [AVA](https://avajs.dev).
export { AIMakeBlank, AIMakeIcon, AIMakeImage, AIMakeLink, AIMakePlaceholder, AIMakeText, Root };␊ export { AIMakeBlank, AIMakeIcon, AIMakeImage, AIMakeLink, AIMakePlaceholder, AIMakeText, Root };␊
`, `,
filePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', entryFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine-v0.8/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js',
},
],
pkgName: 'multiple-exported-component', pkgName: 'multiple-exported-component',
pkgVersion: '1.0.0', pkgVersion: '1.0.0',
sourceType: 'module', sourceType: 'module',
@ -35,10 +30,7 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1 > Snapshot 1
{ {
mainEntry: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', entryFileContent: `import _classCallCheck from "@babel/runtime/helpers/classCallCheck";␊
modules: [
{
fileContent: `import _classCallCheck from "@babel/runtime/helpers/classCallCheck";␊
import _createClass from "@babel/runtime/helpers/createClass";␊ import _createClass from "@babel/runtime/helpers/createClass";␊
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";␊ import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";␊
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";␊ import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";␊
@ -123,9 +115,7 @@ Generated by [AVA](https://avajs.dev).
optionalNumber: 123,␊ optionalNumber: 123,␊
};␊ };␊
export default Demo;`, export default Demo;`,
filePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', entryFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine-v0.8/packages/material-parser/test/fixtures/single-exported-component/es/index.js',
},
],
pkgName: 'single-exported-component', pkgName: 'single-exported-component',
pkgVersion: '1.0.0', pkgVersion: '1.0.0',
sourceType: 'module', sourceType: 'module',

View File

@ -12,7 +12,6 @@ const tsComponent = getFromFixtures('ts-component');
test('materialize single exported component by local', async t => { test('materialize single exported component by local', async t => {
const options: IMaterializeOptions = { const options: IMaterializeOptions = {
cwd: singleExportedComptPath,
entry: singleExportedComptPath, entry: singleExportedComptPath,
accesser: 'local', accesser: 'local',
}; };
@ -24,7 +23,6 @@ test('materialize single exported component by local', async t => {
test('materialize multiple exported component by local', async t => { test('materialize multiple exported component by local', async t => {
const options: IMaterializeOptions = { const options: IMaterializeOptions = {
cwd: multiExportedComptPath,
entry: multiExportedComptPath, entry: multiExportedComptPath,
accesser: 'local', accesser: 'local',
}; };
@ -56,6 +54,17 @@ test('materialize multiple exported component by online', async t => {
t.snapshot(actual); t.snapshot(actual);
}); });
test('materialize @ali/lowcode-editor-skeleton by online', async t => {
const options: IMaterializeOptions = {
entry: '@ali/lowcode-editor-skeleton',
accesser: 'online',
};
const actual = await parse(options);
t.snapshot(actual);
});
test('ts component by local', async t => { test('ts component by local', async t => {
const options: IMaterializeOptions = { const options: IMaterializeOptions = {
entry: tsComponent, entry: tsComponent,

View File

@ -8,7 +8,6 @@ const singleExportedComptPath = getFromFixtures('single-exported-component');
test.serial('scan multiple exported component', async t => { test.serial('scan multiple exported component', async t => {
const actual = await scan({ const actual = await scan({
cwd: multiExportedComptPath,
entry: multiExportedComptPath, entry: multiExportedComptPath,
accesser: 'local', accesser: 'local',
}); });
@ -18,7 +17,6 @@ test.serial('scan multiple exported component', async t => {
test.serial('scan single exported component', async t => { test.serial('scan single exported component', async t => {
const actual = await scan({ const actual = await scan({
cwd: singleExportedComptPath,
entry: singleExportedComptPath, entry: singleExportedComptPath,
accesser: 'local', accesser: 'local',
}); });