This is an automated email from the ASF dual-hosted git repository. sushuang pushed a commit to branch feat-local-config in repository https://gitbox.apache.org/repos/asf/echarts-doc.git
commit 32998345699f8d59551794b700b829a7b71c0a28 Author: 100pah <sushuang0...@gmail.com> AuthorDate: Wed Jul 30 21:26:54 2025 +0800 feat: support local config, not tracked by git. --- .gitignore | 2 + README.md | 12 +- build.js | 343 +---------------------------------------- build.js => build/build-doc.js | 23 +-- build/helper.js | 29 ++++ build/webpack.config.js | 118 ++++++++------ config/env.dev.js | 4 +- package.json | 6 +- src/components/LiveExample.vue | 4 +- 9 files changed, 128 insertions(+), 413 deletions(-) diff --git a/.gitignore b/.gitignore index e3f8c759..1213b7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,8 @@ slides/webgl /public/css /public/zh/documents /public/en/documents +/public/zh/coding-standard-content.html +/public/en/coding-standard-content.html /public/*.html diff --git a/README.md b/README.md index 0aa1c614..165cdca7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,17 @@ It will: ### Local Config -To customize the links of `echarts-examples` and other configurations, you can create a local config file `echarts-doc/config/env.dev-local.js`, which is not tracked by git. The content can be copied from `echarts-doc/config/env.dev.js`, and then modify it as needed. `npm run dev` will use this local config file, if it exists, to replace `echarts-doc/config/env.dev.js`. +To customize the links of `echarts-examples` and other configurations, you can create a local config file `echarts-doc/config/env.dev-local.js`, which is not tracked by git, and its top-level properties will be used to override the corresponding properties of `echarts-doc/config/env.dev.js` when `npm run dev`. + +For example, create a `echarts-doc/config/env.dev-local.js`: +```js +module.exports = { + // These props will override the same props in `echarts-doc/config/env.dev-local.js` + galleryViewPath: 'http://127.0.0.1:3002/en/view.html?local=1&c=', + galleryEditorPath: 'http://127.0.0.1:3002/en/editor.html?local=1&c=', + EMBEDDED_ECHARTS_SCRIPT_URL: 'http://localhost:8001/echarts/echarts/dist/echarts.js', +}; +``` ## Tips About Writing Doc diff --git a/build.js b/build.js index 22e6e729..6430894c 100644 --- a/build.js +++ b/build.js @@ -1,344 +1,5 @@ /** - * ------------------------------------------------------------------------ - * Usage: - * - * ```shell - * node build.js --env asf # build all for asf - * node build.js --env echartsjs # build all for echartsjs. - * node build.js --env localsite # build all for localsite. - * node build.js --env dev # the same as "debug", dev the content of docs. - * # Check `./config` to see the available env - * ``` - * ------------------------------------------------------------------------ + * @file For compatibiliy in case that some CI call this file. May not necessary. */ -const md2json = require('./tool/md2json'); -const {extractDesc} = require('./tool/schemaHelper'); -const fs = require('fs'); -const fse = require('fs-extra'); -const marked = require('marked'); -const copydir = require('copy-dir'); -const chalk = require('chalk'); -// const MarkDownTOCRenderer = require('./tool/MarkDownTOCRenderer'); -const argv = require('yargs').argv; -const path = require('path'); -const assert = require('assert'); -const chokidar = require('chokidar'); -const {debounce} = require('lodash'); -const {getDocJSONPVarNname} = require('./src/shared'); - -const projectDir = __dirname; - -function initEnv() { - let envType = argv.env; - let isDev = argv.dev != null || argv.debug != null || argv.env === 'dev'; - - if (isDev) { - console.warn('============================='); - console.warn('!!! THIS IS IN DEV MODE !!!'); - console.warn('============================='); - envType = 'dev'; - } - - if (!envType) { - throw new Error('--env MUST be specified'); - } - - let config = require('./config/env.' + envType); - - const localOverridePath = './config/env.' + envType + '-local.js'; - if (fs.existsSync(path.join(__dirname, localOverridePath))) { - config = require(localOverridePath); - } - - assert(path.isAbsolute(config.releaseDestDir) && path.isAbsolute(config.ecWWWGeneratedDir)); - - config.envType = envType; - - return config; -} - -const config = initEnv(); - -const languages = ['zh', 'en']; - -config.gl = config.gl || {}; -for (let key in config) { - if (key !== 'gl' && !config.gl.hasOwnProperty(key)) { - config.gl[key] = config[key]; - } -} - -async function md2jsonAsync(opt) { - - var newOpt = Object.assign({ - path: path.join(opt.language, opt.entry, '**/*.md'), - tplEnv: Object.assign({}, config, { - galleryViewPath: config.galleryViewPath.replace('${lang}', opt.language), - galleryEditorPath: config.galleryEditorPath.replace('${lang}', opt.language), - handbookPath: config.handbookPath.replace('${lang}', opt.language) - }), - imageRoot: config.imagePath - }, opt); - - function run(cb) { - md2json(newOpt).then(schema => { - writeSingleSchema(schema, opt.language, opt.entry, false); - writeSingleSchemaPartioned(schema, opt.language, opt.entry, false); - console.log(chalk.green('generated: ' + opt.language + '/' + opt.entry)); - cb && cb(); - }).catch(e => { - console.log(e); - }); - } - - var runDebounced = debounce(run, 500, { - leading: false, - trailing: true - }); - return await new Promise((resolve, reject) => { - run(resolve); - - if (argv.watch) { - chokidar.watch(path.resolve(__dirname, opt.language, opt.entry), { - ignoreInitial: true - }).on('all', (event, path) => { - console.log(path, event); - runDebounced(); - }); - } - }); -} - -function copyAsset() { - - const assetSrcDir = path.resolve(projectDir, 'asset'); - - function doCopy() { - for (let lang of languages) { - const assetDestDir = path.resolve(config.releaseDestDir, `${lang}/documents/asset`); - copydir.sync(assetSrcDir, assetDestDir); - } - } - var doCopyDebounced = debounce(doCopy, 500, { - leading: false, - trailing: true - }); - - doCopy(); - - if (argv.watch) { - chokidar.watch(assetSrcDir, { - ignoreInitial: true - }).on('all', (event, path) => { - console.log(path, event); - doCopyDebounced(); - }); - } - console.log('Copy asset done.'); -} - -async function run() { - - for (let language of languages) { - await md2jsonAsync({ - sectionsAnyOf: ['visualMap', 'dataZoom', 'series', 'graphic.elements', 'dataset.transform'], - entry: 'option', - language - }); - - await md2jsonAsync({ - entry: 'tutorial', - maxDepth: 1, - language - }); - - await md2jsonAsync({ - entry: 'api', - language - }); - - await md2jsonAsync({ - sectionsAnyOf: ['series'], - entry: 'option-gl', - // Overwrite - tplEnv: config.gl, - imageRoot: config.gl.imagePath, - language - }); - } - - console.log('Build doc done.'); - - copyAsset(); - - if (!argv.watch) { // Not in watch dev mode - try { - // TODO Do we need to debug changelog in the doc folder? - buildChangelog(); - buildCodeStandard(); - - copySite(); - } - catch (e) { - console.log('Error happens when copying to dest folders.'); - console.log(e); - } - } - - console.log('All done.'); -} - -function buildChangelog() { - for (let lang of languages) { - const srcPath = path.resolve(projectDir, `${lang}/changelog.md`); - const destPath = path.resolve(config.ecWWWGeneratedDir, `${lang}/documents/changelog-content.html`); - fse.outputFileSync( - destPath, - marked(fs.readFileSync(srcPath, 'utf-8')), - 'utf-8' - ); - console.log(chalk.green('generated: ' + destPath)); - } - console.log('Build changelog done.'); -} - -function buildCodeStandard() { - // support for Chinese character - const customRenderer = new marked.Renderer(); - customRenderer.heading = function(text, level, raw) { - const id = raw.toLowerCase().replace(/[^\w\u4e00-\u9fa5]+/g, '-'); - return `<h${level} id="${id}">${text}</h${level}>\n`; - }; - - for (let lang of languages) { - const codeStandardDestPath = path.resolve(config.ecWWWGeneratedDir, `${lang}/coding-standard-content.html`); - fse.ensureDirSync(path.dirname(codeStandardDestPath)); - fse.outputFileSync( - codeStandardDestPath, - marked(fs.readFileSync(`${lang}/coding-standard.md`, 'utf-8'), { renderer: customRenderer }), - 'utf-8' - ); - console.log(chalk.green('generated: ' + codeStandardDestPath)); - } - - console.log('Build code standard done.'); -} - -function copySite() { - const jsSrcPath = path.resolve(projectDir, 'public/js/doc-bundle.js'); - const cssSrcDir = path.resolve(projectDir, 'public/css'); - - // Copy js and css of doc site. - for (let lang of languages) { - const jsDestPath = path.resolve(config.releaseDestDir, `${lang}/js/doc-bundle.js`); - fse.copySync(jsSrcPath, jsDestPath); - console.log(chalk.green(`js copied to: ${jsDestPath}`)); - - const cssDestDir = path.resolve(config.releaseDestDir, `${lang}/css`); - fse.copySync(cssSrcDir, cssDestDir); - console.log(chalk.green(`css copied to: ${cssDestDir}`)); - } - - console.log('Copy site done.'); -} - -function writeSingleSchema(schema, language, docName, format) { - const destPath = path.resolve(config.releaseDestDir, `${language}/documents/${docName}.json`); - fse.ensureDirSync(path.dirname(destPath)); - fse.outputFileSync( - destPath, - format ? JSON.stringify(schema, null, 2) : JSON.stringify(schema), - 'utf-8' - ); - // console.log(chalk.green('generated: ' + destPath)); -} - -function writeSingleSchemaPartioned(schema, language, docName, format) { - const {outline, descriptions} = extractDesc(schema, docName); - - function convertToJS(basename, filePath) { - const content = fs.readFileSync(filePath, 'utf-8'); - const varName = getDocJSONPVarNname(basename); - const code = `window.${varName} = ${content}`; - fs.writeFileSync(filePath.replace(/\.json$/, '.js'), code, 'utf-8'); - } - - const outlineBasename = `${docName}-outline.json`; - const outlineDestPath = path.resolve(config.releaseDestDir, `${language}/documents/${docName}-parts/${outlineBasename}`); - fse.ensureDirSync(path.dirname(outlineDestPath)); - fse.outputFileSync( - outlineDestPath, - format ? JSON.stringify(outline, null, 2) : JSON.stringify(outline), - 'utf-8' - ); - convertToJS(outlineBasename, outlineDestPath); - - function copyUIControlConfigs(source, target) { - for (let key in source) { - if (target[key]) { - if (source[key].uiControl && !target[key].uiControl) { - target[key].uiControl = source[key].uiControl; - } - if (source[key].exampleBaseOptions && !target[key].exampleBaseOptions) { - target[key].exampleBaseOptions = source[key].exampleBaseOptions; - } - } - else { - // console.error(`Unmatched option path ${key}`); - } - } - } - - function readOptionDesc(language, partKey) { - const descDestPath = path.resolve(config.releaseDestDir, `${language}/documents/${docName}-parts/${partKey}.json`); - try { - const text = fs.readFileSync(descDestPath, 'utf-8'); - return JSON.parse(text); - } - catch(e) { - return; - } - } - - function writeOptionDesc(language, partKey, json) { - const descBasename = `${partKey}.json`; - const descDestPath = path.resolve(config.releaseDestDir, `${language}/documents/${docName}-parts/${descBasename}`); - fse.ensureDirSync(path.dirname(descDestPath)); - fse.outputFileSync( - descDestPath, - format ? JSON.stringify(json, null, 2) : JSON.stringify(json), - 'utf-8' - ); - convertToJS(descBasename, descDestPath); - } - - for (let partKey in descriptions) { - let partDescriptions = descriptions[partKey]; - - // Copy ui control config from zh to english. - if (language === 'zh') { - languages.forEach(function (otherLang) { - if (otherLang === 'zh') { - return; - } - const json = readOptionDesc(otherLang, partKey); - if (json) { - copyUIControlConfigs(partDescriptions, json); - writeOptionDesc(otherLang, partKey, json); - } - }); - } - else { - const json = readOptionDesc('zh', partKey); - if (json) { - copyUIControlConfigs(json, partDescriptions); - } - } - - writeOptionDesc(language, partKey, partDescriptions); - // console.log(chalk.green('generated: ' + descDestPath)); - } -}; - -run(); +require('./build/build-doc.js'); diff --git a/build.js b/build/build-doc.js similarity index 93% copy from build.js copy to build/build-doc.js index 22e6e729..ac0d8e70 100644 --- a/build.js +++ b/build/build-doc.js @@ -12,8 +12,8 @@ * ------------------------------------------------------------------------ */ -const md2json = require('./tool/md2json'); -const {extractDesc} = require('./tool/schemaHelper'); +const md2json = require('../tool/md2json'); +const {extractDesc} = require('../tool/schemaHelper'); const fs = require('fs'); const fse = require('fs-extra'); const marked = require('marked'); @@ -22,12 +22,12 @@ const chalk = require('chalk'); // const MarkDownTOCRenderer = require('./tool/MarkDownTOCRenderer'); const argv = require('yargs').argv; const path = require('path'); -const assert = require('assert'); const chokidar = require('chokidar'); const {debounce} = require('lodash'); -const {getDocJSONPVarNname} = require('./src/shared'); +const {getDocJSONPVarNname} = require('../src/shared'); +const {readConfigEnvFile} = require('./helper'); -const projectDir = __dirname; +const projectDir = path.resolve(__dirname, '..'); function initEnv() { let envType = argv.env; @@ -44,14 +44,7 @@ function initEnv() { throw new Error('--env MUST be specified'); } - let config = require('./config/env.' + envType); - - const localOverridePath = './config/env.' + envType + '-local.js'; - if (fs.existsSync(path.join(__dirname, localOverridePath))) { - config = require(localOverridePath); - } - - assert(path.isAbsolute(config.releaseDestDir) && path.isAbsolute(config.ecWWWGeneratedDir)); + const config = readConfigEnvFile(envType); config.envType = envType; @@ -100,7 +93,7 @@ async function md2jsonAsync(opt) { run(resolve); if (argv.watch) { - chokidar.watch(path.resolve(__dirname, opt.language, opt.entry), { + chokidar.watch(path.resolve(projectDir, opt.language, opt.entry), { ignoreInitial: true }).on('all', (event, path) => { console.log(path, event); @@ -216,7 +209,7 @@ function buildCodeStandard() { fse.ensureDirSync(path.dirname(codeStandardDestPath)); fse.outputFileSync( codeStandardDestPath, - marked(fs.readFileSync(`${lang}/coding-standard.md`, 'utf-8'), { renderer: customRenderer }), + marked(fs.readFileSync(path.resolve(projectDir, `${lang}/coding-standard.md`), 'utf-8'), { renderer: customRenderer }), 'utf-8' ); console.log(chalk.green('generated: ' + codeStandardDestPath)); diff --git a/build/helper.js b/build/helper.js new file mode 100644 index 00000000..6116dd5f --- /dev/null +++ b/build/helper.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +const ENV_TYPE = ['dev', 'localsite', 'asf']; + +/** + * @param envType {string} 'dev' | 'localsite' | 'asf' + */ +exports.readConfigEnvFile = function (envType) { + assert(ENV_TYPE.indexOf(envType) >= 0, 'envType must be one of ' + ENV_TYPE.join(', ')); + + const configEnvRelativePath = '../config/env.' + envType + '.js'; + const configEnvLocalRelativePath = '../config/env.' + envType + '-local.js'; + + const config = require(configEnvRelativePath); + if (fs.existsSync(path.resolve(__dirname, configEnvLocalRelativePath))) { + const configLocal = require(configEnvLocalRelativePath); + assert( + typeof configLocal === 'object' && !Array.isArray(configLocal), + configEnvLocalRelativePath + ' must be an object.' + ); + Object.assign(config, configLocal); + } + + assert(path.isAbsolute(config.releaseDestDir) && path.isAbsolute(config.ecWWWGeneratedDir)); + + return config; +}; diff --git a/build/webpack.config.js b/build/webpack.config.js index 09cbc1ff..e76ee15a 100644 --- a/build/webpack.config.js +++ b/build/webpack.config.js @@ -2,57 +2,73 @@ const webpack = require('webpack'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const {readConfigEnvFile} = require('./helper'); -/** - * @type {import('webpack').Configuration} - */ -module.exports = { - entry: path.resolve(__dirname, '../src/main.js'), - output: { - filename: 'doc-bundle.js', - path: path.resolve(__dirname, '../public/js'), - library: 'echartsDoc', - libraryTarget: 'umd' - }, - stats: 'minimal', - module: { - rules: [{ - test: /\.vue$/, - use: ['vue-loader'] - }, { - test: /\.js$/, - use: ['babel-loader'], - exclude: /node_modules/ - }, { - test: /\.css$/, - use: [MiniCssExtractPlugin.loader, 'css-loader'] - }, { - test: /\.scss$/, - use: [MiniCssExtractPlugin.loader, 'css-loader', 'sassjs-loader'] - }, { - test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/, - use: [{ - loader: 'file-loader', - options: { - limit: 10000, - outputPath: '../css', - name: '[name].[ext]' - } + +module.exports = (env, argv) => { + + const isDev = argv.mode === 'development'; + + let configEnv = {}; + if (isDev) { + configEnv = readConfigEnvFile('dev'); + } + + return { + entry: path.resolve(__dirname, '../src/main.js'), + output: { + filename: 'doc-bundle.js', + path: path.resolve(__dirname, '../public/js'), + library: 'echartsDoc', + libraryTarget: 'umd' + }, + stats: 'minimal', + module: { + rules: [{ + test: /\.vue$/, + use: ['vue-loader'] + }, { + test: /\.js$/, + use: ['babel-loader'], + exclude: /node_modules/ + }, { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'] + }, { + test: /\.scss$/, + use: [MiniCssExtractPlugin.loader, 'css-loader', 'sassjs-loader'] + }, { + test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/, + use: [{ + loader: 'file-loader', + options: { + limit: 10000, + outputPath: '../css', + name: '[name].[ext]' + } + }] }] - }] - }, - externals: { - vue: 'Vue', - codemirror: 'CodeMirror', - 'js-beautify': 'beautifier' - }, - plugins: [ - new webpack.IgnorePlugin({ - resourceRegExp: /^fs$/ - }), - new VueLoaderPlugin(), - new MiniCssExtractPlugin({ - filename: '../css/doc-bundle.css' - }) - ] + }, + externals: { + vue: 'Vue', + codemirror: 'CodeMirror', + 'js-beautify': 'beautifier' + }, + plugins: [ + new webpack.DefinePlugin({ + // It can be used in the code directly. + INJECTED_CONFIG: JSON.stringify({ + EMBEDDED_ECHARTS_SCRIPT_URL: configEnv.EMBEDDED_ECHARTS_SCRIPT_URL, + }) + }), + new webpack.IgnorePlugin({ + resourceRegExp: /^fs$/ + }), + new VueLoaderPlugin(), + new MiniCssExtractPlugin({ + filename: '../css/doc-bundle.css' + }) + ] + }; }; + diff --git a/config/env.dev.js b/config/env.dev.js index 14cba3a5..127270d4 100644 --- a/config/env.dev.js +++ b/config/env.dev.js @@ -13,5 +13,7 @@ module.exports = { }, releaseDestDir: path.resolve(__dirname, '../public'), - ecWWWGeneratedDir: path.resolve(__dirname, '../../echarts-www/_generated') + ecWWWGeneratedDir: path.resolve(__dirname, '../../echarts-www/_generated'), + + EMBEDDED_ECHARTS_SCRIPT_URL: null, }; \ No newline at end of file diff --git a/package.json b/package.json index 9634f9f8..7b21cbcd 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "url": "https://github.com/apache/echarts-doc" }, "scripts": { - "build": "node build.js --env asf", - "watch": "node build.js --env dev --watch", - "localsite": "node build.js --env localsite", + "build": "node build/build-doc.js --env asf", + "watch": "node build/build-doc.js --env dev --watch", + "localsite": "node build/build-doc.js --env localsite", "build:site": "webpack --config build/webpack.config.js --mode production", "watch:site": "webpack --config build/webpack.config.js --mode development --devtool inline-source-map --watch", "server": "node server.js", diff --git a/src/components/LiveExample.vue b/src/components/LiveExample.vue index 03a64684..090ce47d 100644 --- a/src/components/LiveExample.vue +++ b/src/components/LiveExample.vue @@ -86,7 +86,9 @@ let echartsLoadPromise; function fetchECharts() { return echartsLoadPromise || (echartsLoadPromise = new Promise(function (resolve, reject) { const script = document.createElement('script'); - script.src = (window.ECHARTS_WWW_VENDORS_CDN_ROOT || 'https://fastly.jsdelivr.net/npm/') + 'echarts/dist/echarts.min.js'; + script.src = INJECTED_CONFIG.EMBEDDED_ECHARTS_SCRIPT_URL + ? INJECTED_CONFIG.EMBEDDED_ECHARTS_SCRIPT_URL + : (window.ECHARTS_WWW_VENDORS_CDN_ROOT || 'https://fastly.jsdelivr.net/npm/') + 'echarts/dist/echarts.min.js'; script.async = true; script.onload = function () { echartsLoadPromise = null; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org