@next-core/build-next-bricks 1.7.1 → 1.8.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.
@@ -51,6 +51,10 @@ try {
51
51
  path.join(distDir, "manifest.json"),
52
52
  JSON.stringify(config.manifest, null, 2)
53
53
  );
54
+ await writeFile(
55
+ path.join(distDir, "types.json"),
56
+ JSON.stringify(config.types, null, 2)
57
+ );
54
58
  }
55
59
  }
56
60
  }
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { Compiler, Configuration, RuleSetRule, container } from "webpack";
2
2
 
3
3
  export declare function build(config: BuildNextBricksConfig): Compiler;
4
- export declare function getSvgrLoaders(options: {
4
+ export declare function getSvgrLoaders(options?: {
5
5
  /** Set it to true for font icons */
6
6
  convertCurrentColor?: boolean;
7
7
  }): RuleSetRule["use"];
@@ -75,7 +75,7 @@ export interface BuildNextBricksConfig {
75
75
  extractCss?: boolean;
76
76
  /** Treat svg as React component instead of asset */
77
77
  svgAsReactComponent?: boolean;
78
- /** Customize rules for svg */
78
+ /** Customize rules for svg, this will take precedence over `svgAsReactComponent` */
79
79
  svgRules?: RuleSetRule[];
80
80
  /** By default the image assets are named `images/[hash][ext][query]` */
81
81
  imageAssetFilename?: string | ((pathData: any, assetInfo: any) => string);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next-core/build-next-bricks",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Build next bricks",
5
5
  "homepage": "https://github.com/easyops-cn/next-core/tree/v3/packages/build-next-bricks",
6
6
  "license": "GPL-3.0",
@@ -51,5 +51,5 @@
51
51
  "devDependencies": {
52
52
  "@next-core/brick-manifest": "^0.1.0"
53
53
  },
54
- "gitHead": "b6ca0aa67aae992ed7e6866bc1a9bc384cdb8f7b"
54
+ "gitHead": "a2f8ca6b36b45b4a306f4b933e6bc2ca195cdd1f"
55
55
  }
@@ -17,6 +17,7 @@ export default class EmitBricksJsonPlugin {
17
17
  this.processors = options.processors;
18
18
  this.dependencies = options.dependencies;
19
19
  this.manifest = options.manifest;
20
+ this.types = options.types;
20
21
  }
21
22
 
22
23
  /**
@@ -71,6 +72,12 @@ export default class EmitBricksJsonPlugin {
71
72
  new webpack.sources.RawSource(manifestJson, false)
72
73
  );
73
74
 
75
+ const typesJson = JSON.stringify(this.types, null, 2);
76
+ compilation.emitAsset(
77
+ "types.json",
78
+ new webpack.sources.RawSource(typesJson, false)
79
+ );
80
+
74
81
  console.log("Defined bricks:", this.bricks);
75
82
  console.log("Defined elements:", this.elements);
76
83
  console.log("Defined processors:", this.processors);
package/src/build.js CHANGED
@@ -259,7 +259,7 @@ async function getWebpackConfig(config) {
259
259
  "jpg",
260
260
  "jpeg",
261
261
  "gif",
262
- ...(config.svgRules ? [] : ["svg"]),
262
+ ...(config.svgRules || config.svgAsReactComponent ? [] : ["svg"]),
263
263
  ].join("|")})$`,
264
264
  "i"
265
265
  ),
@@ -273,7 +273,7 @@ async function getWebpackConfig(config) {
273
273
  ? [
274
274
  {
275
275
  test: /\.svg$/i,
276
- use: getSvgrLoaders(false),
276
+ use: getSvgrLoaders(),
277
277
  },
278
278
  ]
279
279
  : [])),
@@ -366,6 +366,7 @@ async function getWebpackConfig(config) {
366
366
  processors,
367
367
  dependencies: config.dependencies,
368
368
  manifest: config.manifest,
369
+ types: config.types,
369
370
  }),
370
371
  ]
371
372
  : []),
@@ -235,7 +235,7 @@ function scanFields(manifest, nodes, source) {
235
235
  * @param {Node[]} nodes
236
236
  * @param {string} source
237
237
  */
238
- function parseDocComment(node, source) {
238
+ export function parseDocComment(node, source) {
239
239
  if (node.leadingComments) {
240
240
  const docComment = node.leadingComments.find(
241
241
  (comment) => comment.type === "CommentBlock"
package/src/scanBricks.js CHANGED
@@ -6,6 +6,12 @@ import babelTraverse from "@babel/traverse";
6
6
  import _ from "lodash";
7
7
  import getCamelPackageName from "./getCamelPackageName.js";
8
8
  import makeBrickManifest from "./makeBrickManifest.js";
9
+ import { BASE_TYPE, TS_KEYWORD_LIST, getTypeAnnotation } from "./utils.js";
10
+ import {
11
+ isImportDefaultSpecifier,
12
+ isImportDeclaration,
13
+ isImportSpecifier,
14
+ } from "@babel/types";
9
15
 
10
16
  /**
11
17
  * @typedef {import("@next-core/brick-manifest").PackageManifest} PackageManifest
@@ -26,7 +32,7 @@ const validExposeName = /^[-\w]+$/;
26
32
  * Scan defined bricks by AST.
27
33
  *
28
34
  * @param {string} packageDir
29
- * @returns {Promise<{exposes: Exposes; dependencies: Record<string, string[]>; manifest: PackageManifest}>}
35
+ * @returns {Promise<{exposes: Exposes; dependencies: Record<string, string[]>; manifest: PackageManifest; types: Record<string, unknown>}>}
30
36
  */
31
37
  export default async function scanBricks(packageDir) {
32
38
  /** @type {Map<string, Expose>} */
@@ -62,6 +68,10 @@ export default async function scanBricks(packageDir) {
62
68
  /** @type {Map<string, Set<string>} */
63
69
  const importsMap = new Map();
64
70
 
71
+ const typescriptList = [];
72
+
73
+ const bricksImportsInfo = {};
74
+
65
75
  /**
66
76
  *
67
77
  * @param {string} filePath
@@ -80,6 +90,9 @@ export default async function scanBricks(packageDir) {
80
90
  const extname = path.extname(filePath);
81
91
  const content = await readFile(filePath, "utf-8");
82
92
 
93
+ /** @type {Record<string, string[]>} */
94
+ /** @type {string} */
95
+ let brickFullName;
83
96
  /** @type {ReturnType<typeof import("@babel/parser").parse>} */
84
97
  let ast;
85
98
  try {
@@ -388,6 +401,8 @@ export default async function scanBricks(packageDir) {
388
401
  specifiedDeps[fullName] = deps;
389
402
  }
390
403
 
404
+ brickFullName = fullName;
405
+
391
406
  brickSourceFiles.set(fullName, filePath);
392
407
 
393
408
  manifest.bricks.push(makeBrickManifest(fullName, nodePath, content));
@@ -401,7 +416,7 @@ export default async function scanBricks(packageDir) {
401
416
  });
402
417
  }
403
418
  },
404
- ImportDeclaration({ node: { source, importKind, specifiers } }) {
419
+ ImportDeclaration({ node: { source, importKind } }) {
405
420
  // Match `import "..."`
406
421
  if (
407
422
  source.type === "StringLiteral" &&
@@ -433,6 +448,25 @@ export default async function scanBricks(packageDir) {
433
448
  }
434
449
  }
435
450
  },
451
+ TSInterfaceDeclaration({ node }) {
452
+ if (!node.id) return;
453
+ typescriptList.push({
454
+ ...(getTypeAnnotation(node, content) || {}),
455
+ filePath,
456
+ });
457
+ },
458
+ TSTypeAliasDeclaration({ node }) {
459
+ typescriptList.push({
460
+ ...(getTypeAnnotation(node, content) || {}),
461
+ filePath,
462
+ });
463
+ },
464
+ TSEnumDeclaration: ({ node }) => {
465
+ typescriptList.push({
466
+ ...(getTypeAnnotation(node, content) || {}),
467
+ filePath,
468
+ });
469
+ },
436
470
  });
437
471
 
438
472
  await Promise.all(
@@ -440,6 +474,43 @@ export default async function scanBricks(packageDir) {
440
474
  scanByImport(item, filePath, nextOverrideImport)
441
475
  )
442
476
  );
477
+
478
+ if (brickFullName) {
479
+ const imports = ast.program.body
480
+ .map((item) => {
481
+ if (isImportDeclaration(item)) {
482
+ const { source, specifiers } = item;
483
+ if (
484
+ source.type === "StringLiteral" &&
485
+ (source.value.startsWith("./") ||
486
+ source.value.startsWith("../")) &&
487
+ !source.value.endsWith(".css")
488
+ ) {
489
+ const importPath = path.resolve(dirname, source.value);
490
+ const importKeys = [];
491
+ for (let i = 0; i < specifiers.length; i++) {
492
+ const importItem = specifiers[i];
493
+ if (isImportSpecifier(importItem)) {
494
+ importKeys.push(importItem.imported.name);
495
+ } else if (isImportDefaultSpecifier(importItem)) {
496
+ importKeys.push(importItem.local.name);
497
+ }
498
+ }
499
+ return {
500
+ keys: importKeys,
501
+ path: importPath,
502
+ };
503
+ }
504
+ }
505
+ return false;
506
+ })
507
+ .filter(Boolean);
508
+
509
+ bricksImportsInfo[brickFullName] = {
510
+ imports,
511
+ filePath,
512
+ };
513
+ }
443
514
  }
444
515
 
445
516
  /**
@@ -483,6 +554,45 @@ export default async function scanBricks(packageDir) {
483
554
 
484
555
  await scanByFile(bootstrapTsPath);
485
556
 
557
+ async function checkMissLoadFile() {
558
+ const processedFilesList = [...processedFiles];
559
+ const files = Object.values(bricksImportsInfo)
560
+ .map(({ imports }) => {
561
+ return imports.map(
562
+ (item) =>
563
+ !processedFilesList.find((path) => isMatch(path, item.path)) &&
564
+ item.path
565
+ );
566
+ })
567
+ .flat(1)
568
+ .filter(Boolean);
569
+
570
+ if (files.length) {
571
+ await Promise.all(
572
+ [...new Set(files)].map((file) => {
573
+ const fileName = path.dirname(file);
574
+ const lastName = path.basename(file);
575
+ const matchExtension = /\.[tj]sx?$/.test(lastName);
576
+ const noExtension = !lastName.includes(".");
577
+ let fileInfo;
578
+
579
+ if (matchExtension || noExtension) {
580
+ fileInfo = [fileName, lastName.replace(/\.[^.]+$/, "")];
581
+ }
582
+ if (noExtension && existsSync(file) && statSync(file).isDirectory()) {
583
+ fileInfo = [fileName, "index"];
584
+ }
585
+
586
+ if (fileInfo) {
587
+ return scanByImport(fileInfo, "", "");
588
+ }
589
+ })
590
+ );
591
+ }
592
+ }
593
+
594
+ await checkMissLoadFile();
595
+
486
596
  // console.log("usingWrappedBricks:", usingWrappedBricks);
487
597
  // console.log("brickSourceFiles:", brickSourceFiles);
488
598
  // console.log("importsMap:", importsMap);
@@ -519,6 +629,91 @@ export default async function scanBricks(packageDir) {
519
629
  }
520
630
  }
521
631
 
632
+ function ingoreField(obj) {
633
+ return _.omit(obj, ["filePath", "reference"]);
634
+ }
635
+ function isMatch(importPath, filePath) {
636
+ return (
637
+ importPath.replace(/\.[^.]+$/, "") === filePath.replace(/\.[^.]+$/, "")
638
+ );
639
+ }
640
+
641
+ /**
642
+ *
643
+ * @param {string} type
644
+ * @param {Record<string, unknown} importInfo
645
+ * @param {Set<string>} importKeysSet
646
+ * @param {string} realFilePath
647
+ * @returns void
648
+ */
649
+ function findType(name, importInfo, importKeysSet, realFilePath = "") {
650
+ if (importKeysSet.has(name)) return;
651
+ importKeysSet.add(name);
652
+
653
+ const { imports, filePath } = importInfo;
654
+ const importItem = imports.find((item) => item.keys.includes(name));
655
+ const importPath = realFilePath
656
+ ? realFilePath
657
+ : importItem
658
+ ? importItem.path
659
+ : filePath;
660
+
661
+ const interfaceItem = typescriptList.find(
662
+ (item) => isMatch(item.filePath, importPath) && item.name === name
663
+ );
664
+
665
+ if (interfaceItem) {
666
+ importInfo.types = (importInfo.types || []).concat(
667
+ ingoreField(interfaceItem)
668
+ );
669
+ findRefrenceItem(
670
+ interfaceItem.extends,
671
+ importInfo,
672
+ importKeysSet,
673
+ importPath
674
+ );
675
+ findRefrenceItem(
676
+ interfaceItem.reference,
677
+ importInfo,
678
+ importKeysSet,
679
+ importPath
680
+ );
681
+ }
682
+ }
683
+
684
+ function findRefrenceItem(list, importInfo, importKeySet, realFilePath = "") {
685
+ if (Array.isArray(list) && list.length) {
686
+ list.forEach((item) =>
687
+ findType(item, importInfo, importKeySet, realFilePath)
688
+ );
689
+ }
690
+ }
691
+
692
+ if (manifest && manifest.bricks.length) {
693
+ manifest.bricks.forEach((brickDoc) => {
694
+ const { name, properties } = brickDoc;
695
+ const importInfo = bricksImportsInfo[name];
696
+ const fieldTypes = properties
697
+ .map(
698
+ ({ type }) =>
699
+ type &&
700
+ type
701
+ .match(/\w+/g)
702
+ .filter(
703
+ (item) => !(BASE_TYPE[item] || TS_KEYWORD_LIST.includes(item))
704
+ )
705
+ )
706
+ .flat(1);
707
+
708
+ const importKeysSet = new Set();
709
+ if (Array.isArray(fieldTypes) && fieldTypes.length) {
710
+ [...new Set(fieldTypes)].forEach((type) =>
711
+ findType(type, importInfo, importKeysSet)
712
+ );
713
+ }
714
+ });
715
+ }
716
+
522
717
  // console.log("exposes:", exposes);
523
718
 
524
719
  return {
@@ -528,5 +723,10 @@ export default async function scanBricks(packageDir) {
528
723
  ...specifiedDeps,
529
724
  },
530
725
  manifest,
726
+ types: Object.fromEntries(
727
+ Object.entries(bricksImportsInfo).map(([k, v]) => {
728
+ return [k, v.types];
729
+ })
730
+ ),
531
731
  };
532
732
  }
package/src/utils.js ADDED
@@ -0,0 +1,227 @@
1
+ import {
2
+ isIdentifier,
3
+ isStringLiteral,
4
+ isTSArrayType,
5
+ isTSEnumDeclaration,
6
+ isTSEnumMember,
7
+ isTSIndexSignature,
8
+ isTSIndexedAccessType,
9
+ isTSInterfaceDeclaration,
10
+ isTSIntersectionType,
11
+ isTSLiteralType,
12
+ isTSPropertySignature,
13
+ isTSQualifiedName,
14
+ isTSTupleType,
15
+ isTSTypeAliasDeclaration,
16
+ isTSTypeAnnotation,
17
+ isTSTypeLiteral,
18
+ isTSTypeParameter,
19
+ isTSTypeParameterDeclaration,
20
+ isTSTypeReference,
21
+ isTSUnionType,
22
+ } from "@babel/types";
23
+ import _ from "lodash";
24
+ import { parseDocComment } from "./makeBrickManifest.js";
25
+
26
+ export const BASE_TYPE = {
27
+ any: "any",
28
+ boolean: "boolean",
29
+ undefined: "undefined",
30
+ bigint: "bigint",
31
+ null: "null",
32
+ number: "number",
33
+ object: "object",
34
+ string: "string",
35
+ };
36
+
37
+ export const TS_KEYWORD_LIST = ["Record", "Array", "unknow", "void"];
38
+ const TS_BASIC_TYPE = {
39
+ TSAnyKeyword: BASE_TYPE.any,
40
+ TSBooleanKeyword: BASE_TYPE.boolean,
41
+ TSBigIntKeyword: BASE_TYPE.bigint,
42
+ TSNullKeyword: BASE_TYPE.null,
43
+ TSNumberKeyword: BASE_TYPE.number,
44
+ TSObjectKeyword: BASE_TYPE.object,
45
+ TSStringKeyword: BASE_TYPE.string,
46
+ TSUndefinedKeyword: BASE_TYPE.undefined,
47
+ TSUnknownKeyword: BASE_TYPE.any,
48
+ };
49
+
50
+ function getTSBasicType(annotation) {
51
+ return annotation && TS_BASIC_TYPE[annotation.type];
52
+ }
53
+
54
+ const getDocComment = (typeAnnotation, source) => {
55
+ return parseDocComment(typeAnnotation, source) || {};
56
+ };
57
+
58
+ /**
59
+ *
60
+ * @param {import("@babel/types").typeAnnotation} typeAnnotation
61
+ * @param {string} source
62
+ * @param {Set<string>} reference
63
+ * @returns
64
+ */
65
+ export const getTypeAnnotation = (
66
+ typeAnnotation,
67
+ source,
68
+ reference = new Set()
69
+ ) => {
70
+ const makeResultWithDocComment = (result) => {
71
+ return {
72
+ ...result,
73
+ ...getDocComment(typeAnnotation, source),
74
+ };
75
+ };
76
+ /**
77
+ * @param {import("@babel/types").typeAnnotation} typeAnnotation
78
+ */
79
+ const walkTypeAnnotation = (typeAnnotation) => {
80
+ if (isTSInterfaceDeclaration(typeAnnotation)) {
81
+ const {
82
+ id,
83
+ body,
84
+ extends: extendsItems,
85
+ typeParameters,
86
+ } = typeAnnotation;
87
+ if (!id) return;
88
+ return makeResultWithDocComment({
89
+ name: id.name,
90
+ type: "interface",
91
+ typeParameters: typeParameters && walkTypeAnnotation(typeParameters),
92
+ annotation: body.body.map(walkTypeAnnotation),
93
+ extends: extendsItems?.map((item) => item.expression.name),
94
+ reference: [...reference],
95
+ });
96
+ } else if (isTSTypeAliasDeclaration(typeAnnotation)) {
97
+ const { id, typeParameters } = typeAnnotation;
98
+ return makeResultWithDocComment({
99
+ name: id.name,
100
+ type: "typeAlias",
101
+ typeParameters: typeParameters && walkTypeAnnotation(typeParameters),
102
+ annotation: walkTypeAnnotation(typeAnnotation.typeAnnotation),
103
+ reference: [...reference],
104
+ });
105
+ } else if (isTSEnumDeclaration(typeAnnotation)) {
106
+ const { id, members } = typeAnnotation;
107
+ return makeResultWithDocComment({
108
+ name: id.name,
109
+ type: "enums",
110
+ members: members?.map(walkTypeAnnotation),
111
+ reference: [...reference],
112
+ });
113
+ } else if (isTSEnumMember(typeAnnotation)) {
114
+ return {
115
+ name: walkTypeAnnotation(typeAnnotation.id),
116
+ value: walkTypeAnnotation(typeAnnotation.initializer),
117
+ };
118
+ } else if (isTSTypeAnnotation(typeAnnotation)) {
119
+ return walkTypeAnnotation(typeAnnotation.typeAnnotation);
120
+ } else if (isTSTypeReference(typeAnnotation)) {
121
+ const { typeName, typeParameters } = typeAnnotation;
122
+ const qualified = isTSQualifiedName(typeName)
123
+ ? walkTypeAnnotation(typeName)
124
+ : undefined;
125
+ const name = typeName.name;
126
+ reference.add(name);
127
+ const params =
128
+ typeParameters?.params && typeParameters.params.map(walkTypeAnnotation);
129
+ return makeResultWithDocComment({
130
+ type: "reference",
131
+ typeName: name,
132
+ typeParameters: params,
133
+ qualified,
134
+ });
135
+ } else if (isTSQualifiedName(typeAnnotation)) {
136
+ const left = walkTypeAnnotation(typeAnnotation.left);
137
+ if (typeof left === "string") {
138
+ reference.add(left);
139
+ }
140
+ return {
141
+ type: "qualifiedName",
142
+ left: left,
143
+ right: walkTypeAnnotation(typeAnnotation.right),
144
+ };
145
+ } else if (isTSUnionType(typeAnnotation)) {
146
+ return makeResultWithDocComment({
147
+ type: "union",
148
+ types: typeAnnotation.types.map(walkTypeAnnotation),
149
+ });
150
+ } else if (isTSArrayType(typeAnnotation)) {
151
+ return makeResultWithDocComment({
152
+ type: "array",
153
+ elementType: walkTypeAnnotation(typeAnnotation.elementType),
154
+ });
155
+ } else if (isTSTupleType(typeAnnotation)) {
156
+ return makeResultWithDocComment({
157
+ type: "tuple",
158
+ elementTypes: typeAnnotation.elementTypes.map(walkTypeAnnotation),
159
+ });
160
+ } else if (isTSIntersectionType(typeAnnotation)) {
161
+ return makeResultWithDocComment({
162
+ type: "intersection",
163
+ types: typeAnnotation.types.map(walkTypeAnnotation),
164
+ });
165
+ } else if (isTSTypeLiteral(typeAnnotation)) {
166
+ return makeResultWithDocComment({
167
+ type: "typeLiteral",
168
+ members: typeAnnotation.members.map(walkTypeAnnotation),
169
+ });
170
+ } else if (isTSPropertySignature(typeAnnotation)) {
171
+ return makeResultWithDocComment({
172
+ type: "propertySignature",
173
+ name: typeAnnotation.key.name,
174
+ property: walkTypeAnnotation(typeAnnotation.typeAnnotation),
175
+ });
176
+ } else if (isTSIndexSignature(typeAnnotation)) {
177
+ return makeResultWithDocComment({
178
+ type: "indexSignature",
179
+ parameters: {
180
+ name: typeAnnotation.parameters[0].name,
181
+ ...walkTypeAnnotation(typeAnnotation.parameters[0].typeAnnotation),
182
+ },
183
+ property: walkTypeAnnotation(typeAnnotation.typeAnnotation),
184
+ });
185
+ } else if (isTSIndexedAccessType(typeAnnotation)) {
186
+ return makeResultWithDocComment({
187
+ type: "indexedAccess",
188
+ objectType: walkTypeAnnotation(typeAnnotation.objectType),
189
+ indexType: walkTypeAnnotation(typeAnnotation.indexType),
190
+ });
191
+ } else if (isTSTypeParameterDeclaration(typeAnnotation)) {
192
+ return makeResultWithDocComment({
193
+ type: "typeParameterDeclaration",
194
+ params: typeAnnotation.params.map(walkTypeAnnotation),
195
+ });
196
+ } else if (isTSTypeParameter(typeAnnotation)) {
197
+ return makeResultWithDocComment({
198
+ type: "typeParameter",
199
+ value: typeAnnotation.name,
200
+ default:
201
+ typeAnnotation.default && walkTypeAnnotation(typeAnnotation.default),
202
+ });
203
+ } else if (isTSLiteralType(typeAnnotation)) {
204
+ return makeResultWithDocComment({
205
+ type: "literal",
206
+ value: typeAnnotation.literal.value,
207
+ });
208
+ } else if (isIdentifier(typeAnnotation)) {
209
+ return {
210
+ type: "identifier",
211
+ value: typeAnnotation.name,
212
+ };
213
+ } else if (isStringLiteral(typeAnnotation)) {
214
+ return {
215
+ type: "stringLiteral",
216
+ value: typeAnnotation.value,
217
+ };
218
+ } else if (getTSBasicType(typeAnnotation)) {
219
+ return makeResultWithDocComment({
220
+ type: "keyword",
221
+ value: getTSBasicType(typeAnnotation),
222
+ });
223
+ }
224
+ };
225
+
226
+ return walkTypeAnnotation(typeAnnotation);
227
+ };