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