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 <bm2736...@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-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org