You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/12/22 02:42:41 UTC
[incubator-echarts-examples] branch next updated: generate options
of examples. add bundling test
This is an automated email from the ASF dual-hosted git repository.
shenyi pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-echarts-examples.git
The following commit(s) were added to refs/heads/next by this push:
new ca32bcf generate options of examples. add bundling test
ca32bcf is described below
commit ca32bcf136206779b828fd275b6e3e0c4a7b36a7
Author: pissang <bm...@gmail.com>
AuthorDate: Tue Dec 22 10:38:21 2020 +0800
generate options of examples. add bundling test
---
.gitignore | 3 +
common/optionDeps.js | 178 ++++++++++++++++++++++++++++++++++++++++
common/task.js | 46 +++++++++++
package.json | 3 +
public/screenshot.html | 16 ++++
test/README.md | 3 +
test/main.js | 69 ++++++++++++++++
tool/build-example.js | 218 +++++++++++++++++++++++++++----------------------
8 files changed, 440 insertions(+), 96 deletions(-)
diff --git a/.gitignore b/.gitignore
index c8cedd0..8e50c5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ public/vendors/echarts/map/raw
/public/js
/public/css
/public/asset
+/public/data/option
+
+tmp
\ No newline at end of file
diff --git a/common/optionDeps.js b/common/optionDeps.js
new file mode 100644
index 0000000..0ce7ab0
--- /dev/null
+++ b/common/optionDeps.js
@@ -0,0 +1,178 @@
+const COMPONENTS_MAP = {
+ grid: 'GridComponent',
+ polar: 'PolarComponent',
+ geo: 'GeoComponent',
+ singleAxis: 'SingleAxisComponent',
+ parallel: 'ParallelComponent',
+ calendar: 'CalendarComponent',
+ graphic: 'GraphicComponent',
+ toolbox: 'ToolboxComponent',
+ tooltip: 'TooltipComponent',
+ axisPointer: 'AxisPointerComponent',
+ brush: 'BrushComponent',
+ title: 'TitleComponent',
+ timeline: 'TimelineComponent',
+ markPoint: 'MarkPointComponent',
+ markLine: 'MarkLineComponent',
+ markArea: 'MarkAreaComponent',
+ legend: 'LegendComponent',
+ dataZoom: 'DataZoomComponent',
+ visualMap: 'VisualMapComponent',
+ aria: 'AriaComponent',
+ dataset: 'DatasetComponent',
+
+ // Dependencies
+ xAxis: 'GridComponent',
+ yAxis: 'GridComponent',
+ angleAxis: 'PolarComponent',
+ radiusAxis: 'PolarComponent'
+}
+const CHARTS_MAP = {
+ line: 'LineChart',
+ bar: 'BarChart',
+ pie: 'PieChart',
+ scatter: 'ScatterChart',
+ radar: 'RadarChart',
+ map: 'MapChart',
+ tree: 'TreeChart',
+ treemap: 'TreemapChart',
+ graph: 'GraphChart',
+ gauge: 'GaugeChart',
+ funnel: 'FunnelChart',
+ parallel: 'ParallelChart',
+ sankey: 'SankeyChart',
+ boxplot: 'BoxplotChart',
+ candlestick: 'CandlestickChart',
+ effectScatter: 'EffectScatterChart',
+ lines: 'LinesChart',
+ heatmap: 'HeatmapChart',
+ pictorialBar: 'PictorialBarChart',
+ themeRiver: 'ThemeRiverChart',
+ sunburst: 'SunburstChart',
+ custom: 'CustomChart'
+}
+
+module.exports.collectDeps = function collectDeps(option) {
+ let deps = [];
+ if (option.options) {
+ option.options.forEach((opt) => {
+ deps = deps.concat(collectDeps(opt));
+ });
+
+ if (option.baseOption) {
+ deps = deps.concat(collectDeps(option.baseOption))
+ }
+
+ return deps;
+ }
+
+ Object.keys(option).forEach((key) => {
+ if (COMPONENTS_MAP[key]) {
+ deps.push(COMPONENTS_MAP[key]);
+ }
+ });
+
+ let series = option.series;
+ if (!Array.isArray(series)) {
+ series = [series];
+ }
+
+ series.forEach((seriesOpt) => {
+ if (CHARTS_MAP[seriesOpt.type]) {
+ deps.push(CHARTS_MAP[seriesOpt.type]);
+ }
+ ['markLine', 'markArea', 'markPoint'].forEach(markerType => {
+ if (seriesOpt[markerType]) {
+ deps.push(COMPONENTS_MAP[markerType]);
+ }
+ });
+ });
+
+ // Remove duplicates
+ return Array.from(new Set(deps));
+}
+
+module.exports.buildPartialImportCode = function (deps, includeType) {
+ const componentsImports = [];
+ const chartsImports = [];
+ const renderersImports = [];
+ deps.forEach(function (dep) {
+ if (dep.endsWith('Renderer')) {
+ renderersImports.push(dep);
+ }
+ else if (dep.endsWith('Chart')) {
+ chartsImports.push(dep);
+ if (includeType) {
+ chartsImports.push(dep.replace(/Chart$/, 'SeriesOption'));
+ }
+ }
+ else if (dep.endsWith('Component')) {
+ componentsImports.push(dep);
+ if (includeType) {
+ componentsImports.push(dep.replace(/Component$/, 'ComponentOption'));
+ }
+ }
+ });
+
+ function getImportsPartCode(imports) {
+ return `${imports.map(str => `
+ ${str}`).join(',')}`;
+ }
+
+ const allImports = [
+ ...componentsImports,
+ ...chartsImports,
+ ...renderersImports
+ ];
+
+ const ECOptionTypeCode = `
+type ECOption = echarts.ComposeOption<
+ ${allImports.filter(a => a.endsWith('Option')).join(' | ')}
+>
+ `;
+
+ return `
+import * as echarts from 'echarts/core';
+
+import {${getImportsPartCode(componentsImports)}
+} from 'echarts/components';
+
+import {${getImportsPartCode(chartsImports)}
+} from 'echarts/charts';
+
+import {${getImportsPartCode(renderersImports)}
+} from 'echarts/renderers';
+
+echarts.use(
+ [${allImports.filter(a => !a.endsWith('Option')).join(', ')}]
+);
+` + (includeType ? ECOptionTypeCode : '')
+}
+
+module.exports.buildLegacyPartialImportCode = function (deps, isESM) {
+ const rootFolder = isESM ? 'esm' : 'lib';
+ const modules = [];
+ deps.forEach(function (dep) {
+ if (dep.endsWith('Renderers')) {
+ modules.push(`zrender/${rootFolder}/${dep}/${dep}`);
+ }
+ else if (dep.endsWith('Chart')) {
+ modules.push(`echarts/${rootFolder}/chart/${dep}`);
+ }
+ else if (dep.endsWith('Component')) {
+ modules.push(`echarts/${rootFolder}/component/${dep}`);
+ }
+ });
+
+ return isESM ? `
+import * as echarts from 'echarts/${rootFolder}/echarts';
+${modules.map(mod => {
+ return `import '${mod}';`;
+}).join('\n')}
+` : `
+const echarts = require('echarts/${rootFolder}/echarts');
+${modules.map(mod => {
+ return `require('${mod}');`;
+}).join('\n')}
+`
+}
\ No newline at end of file
diff --git a/common/task.js b/common/task.js
new file mode 100644
index 0000000..c04e176
--- /dev/null
+++ b/common/task.js
@@ -0,0 +1,46 @@
+function runTasks(
+ taskParamsLists, createTask, concurrency
+) {
+ concurrency = Math.min(taskParamsLists.length, concurrency);
+ return new Promise((resolve, reject) => {
+ let runningTaskCount = 0;
+ let cursor = 0;
+ let rets = [];
+
+ function finishTask(res, idx) {
+ rets[idx] = res;
+ processNext();
+ }
+
+ function failTask(e) {
+ console.error(e);
+ processNext();
+ }
+
+ function processNext() {
+ runningTaskCount--;
+ addTask();
+
+ if (runningTaskCount === 0) {
+ resolve(rets);
+ }
+ }
+
+ function addTask() {
+ const param = taskParamsLists[cursor];
+ if (param) {
+ runningTaskCount++;
+ createTask(param)
+ .then((res) => finishTask(res, cursor))
+ .catch(failTask);
+ cursor++;
+ }
+ }
+
+ for (let i = 0; i < concurrency; i++) {
+ addTask();
+ }
+ });
+}
+
+module.exports.runTasks = runTasks;
\ No newline at end of file
diff --git a/package.json b/package.json
index bd67a94..37bd08e 100644
--- a/package.json
+++ b/package.json
@@ -14,12 +14,14 @@
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
+ "@typescript-eslint/typescript-estree": "^4.10.0",
"argparse": "^1.0.9",
"babel-loader": "^8.1.0",
"chalk": "^3.0.0",
"concurrently": "^5.3.0",
"css-loader": "^3.5.3",
"cwebp-bin": "^6.1.1",
+ "echarts": "^5.0.0",
"file-loader": "^4.3.0",
"fs-extra": "^8.1.0",
"globby": "^10.0.1",
@@ -28,6 +30,7 @@
"mini-css-extract-plugin": "^0.8.2",
"node-static": "^0.7.11",
"open": "^7.1.0",
+ "prettier": "^2.2.1",
"sass.js": "^0.11.1",
"sassjs-loader": "^2.0.0",
"sharp": "^0.26.2",
diff --git a/public/screenshot.html b/public/screenshot.html
index 42d4971..7397d7d 100644
--- a/public/screenshot.html
+++ b/public/screenshot.html
@@ -39,6 +39,22 @@
});
var myChart = echarts.init(document.getElementById('viewport'), params.t || null);
+ var _$oldSetOption = myChart.setOption;
+ var _$finalOption;
+ myChart.setOption = function (option, notMerge) {
+ if (!_$finalOption || notMerge === true || (notMerge && notMerge.notMerge)) {
+ _$finalOption = option;
+ }
+ else {
+ // TODO Should be same logic with echarts merge.
+ _$finalOption = echarts.util.merge(_$finalOption, option);
+ }
+
+ _$oldSetOption.apply(this, arguments);
+ }
+ var _$getEChartsOption = function () {
+ return _$finalOption;
+ }
var app = {};
</script>
<script>
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..02da1ff
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,3 @@
+# Tests
+
+Run all the examples to test module importing, partial bundling and DTS correctness.
diff --git a/test/main.js b/test/main.js
new file mode 100644
index 0000000..a5ff64d
--- /dev/null
+++ b/test/main.js
@@ -0,0 +1,69 @@
+const fs = require('fs');
+const globby = require('globby');
+const {collectDeps, buildPartialImportCode, buildLegacyPartialImportCode} = require('../common/optionDeps');
+const nodePath = require('path');
+const { runTasks } = require('../common/task');
+const fse = require('fs-extra');
+const prettier = require('prettier');
+
+const RUN_CODE_DIR = __dirname + '/tmp/tests';
+
+async function buildRunCode() {
+ const root = `${__dirname}/../public/`;
+ const files = await globby(`${root}/data/option/*.json`);
+
+ console.log('Generating codes');
+ fse.ensureDirSync(RUN_CODE_DIR);
+ const testsList = await runTasks(files, async (fileName) => {
+ const optionCode = await fse.readFile(fileName, 'utf-8');
+ const option = JSON.parse(optionCode);
+
+ const deps = collectDeps(option).concat([
+ // TODO SVG
+ 'CanvasRenderer'
+ ]);
+
+ const commonCode = `
+const myChart = echarts.init(document.getElementById('main'));
+option = ${optionCode}
+myChart.setOption(option);
+ `
+ const legacyCode = `
+${buildLegacyPartialImportCode(deps, true)}
+let option;
+${commonCode}
+ `;
+ const tsCode = `
+${buildPartialImportCode(deps, true)}
+let option: ECOption;
+${commonCode}
+`;
+ const testName = nodePath.basename(fileName, '.json');
+ const tsFile = nodePath.join(RUN_CODE_DIR, testName + '.ts');
+
+ await fse.writeFile(
+ tsFile,
+ prettier.format(tsCode, {
+ parser: 'typescript'
+ }), 'utf-8'
+ );
+ await fse.writeFile(
+ nodePath.join(RUN_CODE_DIR, testName + '.legacy.js'),
+ prettier.format(legacyCode, {
+ parser: 'babel'
+ }), 'utf-8'
+ );
+ console.log('Generated: ', nodePath.join(RUN_CODE_DIR, testName + '.ts'));
+ return tsFile;
+ }, 20);
+
+ return testsList;
+}
+
+async function main() {
+ await buildRunCode();
+}
+
+main().catch(e => {
+ console.error(e);
+})
\ No newline at end of file
diff --git a/tool/build-example.js b/tool/build-example.js
index f548824..21ac883 100644
--- a/tool/build-example.js
+++ b/tool/build-example.js
@@ -1,5 +1,5 @@
const fs = require('fs');
-const glob = require('glob');
+const globby = require('globby');
const path = require('path');
const puppeteer = require('puppeteer');
const matter = require('gray-matter');
@@ -10,6 +10,20 @@ const cwebpBin = require('cwebp-bin');
const util = require('util');
const chalk = require('chalk');
const sharp = require('sharp');
+const fse = require('fs-extra');
+
+function optionToJson(obj, prop) {
+ let json = JSON.stringify(obj, function(key, value) {
+ if (typeof value === 'function') {
+ return 'expr: ' + value.toString();
+ }
+ return value;
+ }, 2);
+ return json;
+};
+function codeSize(code) {
+ return Buffer.byteLength(code, 'utf-8');
+}
const parser = new argparse.ArgumentParser({
addHelp: true
@@ -119,7 +133,22 @@ async function takeScreenshot(
const fileBase = `${rootDir}public/${sourceFolder}/${thumbFolder}/${basename}`;
const filePathTmp = `${fileBase}-tmp.png`;
const filePath = `${fileBase}.png`;
+
+ // Save option for further tests.
+ const option = await page.evaluate(() => {
+ return _$getEChartsOption()
+ });
+ const optionStr = optionToJson(option);
+ if (codeSize(optionStr) > 300 * 1024) {
+ console.log(`${basename} excceeds 300kb. Not save to option json`);
+ }
+ else {
+ fse.ensureDirSync(`${rootDir}public/${sourceFolder}/option/`);
+ fs.writeFileSync(`${rootDir}public/${sourceFolder}/option/${basename}.json`, optionStr, 'utf-8');
+ }
+
console.log(filePath);
+
await page.screenshot({
path: filePathTmp,
type: 'png'
@@ -143,7 +172,6 @@ async function takeScreenshot(
// return;
let browser;
- // https://github.com/GoogleChrome/puppeteer/issues/1260
if (BUILD_THUMBS) {
browser = await puppeteer.launch({
headless: false,
@@ -159,121 +187,119 @@ async function takeScreenshot(
// TODO puppeteer will have Navigation Timeout Exceeded: 30000ms exceeded error in these examples.
const screenshotBlackList = [];
+ const files = await globby(`${rootDir}public/${sourceFolder}/*.js`);
- glob(`${rootDir}public/${sourceFolder}/*.js`, async function (err, files) {
-
- const exampleList = [];
+ const exampleList = [];
- const threadNum = BUILD_THUMBS ? 16 : 1;
- let buckets = [];
- for (let i = 0; i < files.length;) {
- const bucket = [];
- for (let k = 0; k < threadNum; k++) {
- const fileName = files[i++];
- if (!fileName) {
- continue;
- }
- const basename = path.basename(fileName, '.js');
+ const threadNum = BUILD_THUMBS ? 16 : 1;
+ let buckets = [];
+ for (let i = 0; i < files.length;) {
+ const bucket = [];
+ for (let k = 0; k < threadNum; k++) {
+ const fileName = files[i++];
+ if (!fileName) {
+ continue;
+ }
+ const basename = path.basename(fileName, '.js');
- if (
- !matchPattern || matchPattern.some(function (pattern) {
- return minimatch(basename, pattern);
- })
- ) {
- bucket.push({
- buildThumb: BUILD_THUMBS && screenshotBlackList.indexOf(basename) < 0,
- basename
- });
- }
+ if (
+ !matchPattern || matchPattern.some(function (pattern) {
+ return minimatch(basename, pattern);
+ })
+ ) {
+ bucket.push({
+ buildThumb: BUILD_THUMBS && screenshotBlackList.indexOf(basename) < 0,
+ basename
+ });
}
- buckets.push(bucket);
}
+ buckets.push(bucket);
+ }
- for (let theme of themeList) {
- for (let bucket of buckets) {
- const promises = [];
+ for (let theme of themeList) {
+ for (let bucket of buckets) {
+ const promises = [];
- for (const {basename, buildThumb} of bucket) {
+ for (const {basename, buildThumb} of bucket) {
- // Remove mapbox temporary
- if (basename.indexOf('mapbox') >= 0
- || basename.indexOf('shanghai') >= 0
- || basename === 'lines3d-taxi-routes-of-cape-town'
- || basename === 'lines3d-taxi-chengdu'
- || basename === 'map3d-colorful-cities'
- ) {
- continue;
- }
+ // Remove mapbox temporary
+ if (basename.indexOf('mapbox') >= 0
+ || basename.indexOf('shanghai') >= 0
+ || basename === 'lines3d-taxi-routes-of-cape-town'
+ || basename === 'lines3d-taxi-chengdu'
+ || basename === 'map3d-colorful-cities'
+ ) {
+ continue;
+ }
- let fmResult;
- try {
- const code = fs.readFileSync(`${rootDir}public/${sourceFolder}/${basename}.js`, 'utf-8');
- fmResult = matter(code, {
- delimiters: ['/*', '*/']
- });
- }
- catch (e) {
- fmResult = {
- data: {}
- };
- }
+ let fmResult;
+ try {
+ const code = fs.readFileSync(`${rootDir}public/${sourceFolder}/${basename}.js`, 'utf-8');
+ fmResult = matter(code, {
+ delimiters: ['/*', '*/']
+ });
+ }
+ catch (e) {
+ fmResult = {
+ data: {}
+ };
+ }
- // const descHTML = marked(fmResult.body);
+ // const descHTML = marked(fmResult.body);
- try {
- const difficulty = fmResult.data.difficulty != null ? fmResult.data.difficulty : 10;
- const category = (fmResult.data.category || '').split(/,/g).map(a => a.trim()).filter(a => !!a);
- if (!exampleList.find(item => item.id === basename)) { // Avoid add mulitple times when has multiple themes.
- exampleList.push({
- category: category,
- id: basename,
- tags: (fmResult.data.tags || '').split(/,/g).map(a => a.trim()).filter(a => !!a),
- theme: fmResult.data.theme,
- title: fmResult.data.title,
- titleCN: fmResult.data.titleCN,
- difficulty: +difficulty
- });
- }
- // Do screenshot
- if (buildThumb) {
- promises.push(takeScreenshot(
- browser,
- theme,
- rootDir,
- basename,
- fmResult.data.shotWidth,
- fmResult.data.shotDelay
- ));
- }
+ try {
+ const difficulty = fmResult.data.difficulty != null ? fmResult.data.difficulty : 10;
+ const category = (fmResult.data.category || '').split(/,/g).map(a => a.trim()).filter(a => !!a);
+ if (!exampleList.find(item => item.id === basename)) { // Avoid add mulitple times when has multiple themes.
+ exampleList.push({
+ category: category,
+ id: basename,
+ tags: (fmResult.data.tags || '').split(/,/g).map(a => a.trim()).filter(a => !!a),
+ theme: fmResult.data.theme,
+ title: fmResult.data.title,
+ titleCN: fmResult.data.titleCN,
+ difficulty: +difficulty
+ });
}
- catch (e) {
- await browser.close();
- throw new Error(e.toString());
+ // Do screenshot
+ if (buildThumb) {
+ promises.push(takeScreenshot(
+ browser,
+ theme,
+ rootDir,
+ basename,
+ fmResult.data.shotWidth,
+ fmResult.data.shotDelay
+ ));
}
}
- if (promises.length) {
- await Promise.all(promises);
+ catch (e) {
+ await browser.close();
+ throw new Error(e.toString());
}
}
+ if (promises.length) {
+ await Promise.all(promises);
+ }
}
+ }
- if (BUILD_THUMBS) {
- await browser.close();
- }
+ if (BUILD_THUMBS) {
+ await browser.close();
+ }
- exampleList.sort(function (a, b) {
- if (a.difficulty === b.difficulty) {
- return a.id.localeCompare(b.id);
- }
- return a.difficulty - b.difficulty;
- });
+ exampleList.sort(function (a, b) {
+ if (a.difficulty === b.difficulty) {
+ return a.id.localeCompare(b.id);
+ }
+ return a.difficulty - b.difficulty;
+ });
- const code = `
+ const code = `
/* eslint-disable */
// THIS FILE IS GENERATED, DON'T MODIFY */
export default ${JSON.stringify(exampleList, null, 2)}`;
- if (!matchPattern) {
- fs.writeFileSync(path.join(__dirname, `../src/data/chart-list-${sourceFolder}.js`), code, 'utf-8');
- }
- });
+ if (!matchPattern) {
+ fs.writeFileSync(path.join(__dirname, `../src/data/chart-list-${sourceFolder}.js`), code, 'utf-8');
+ }
})();
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org