| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /** |
| * [Create CommonJS files]: |
| * Compatible with prevoius folder structure: `echarts/lib` exists in `node_modules` |
| * (1) Build all files to CommonJS to `echarts/lib`. |
| * (2) Remove __DEV__. |
| * (3) Mount `echarts/src/export.js` to `echarts/lib/echarts.js`. |
| * |
| * [Create ESModule files]: |
| * Build all files to CommonJS to `echarts/esm`. |
| */ |
| |
| const nodePath = require('path'); |
| const assert = require('assert'); |
| const fs = require('fs'); |
| const fsExtra = require('fs-extra'); |
| const chalk = require('chalk'); |
| const ts = require('typescript'); |
| const globby = require('globby'); |
| const transformDEVUtil = require('./transform-dev'); |
| const preamble = require('./preamble'); |
| const dts = require('@lang/rollup-plugin-dts').default; |
| const rollup = require('rollup'); |
| |
| const ecDir = nodePath.resolve(__dirname, '..'); |
| const tmpDir = nodePath.resolve(ecDir, 'pre-publish-tmp'); |
| |
| const tsConfig = readTSConfig(); |
| |
| const autoGeneratedFileAlert = ` |
| /** |
| * AUTO-GENERATED FILE. DO NOT MODIFY. |
| */ |
| |
| `; |
| |
| const mainSrcGlobby = { |
| patterns: [ |
| 'src/**/*.ts' |
| ], |
| cwd: ecDir |
| }; |
| const extensionSrcGlobby = { |
| patterns: [ |
| 'extension-src/**/*.ts' |
| ], |
| cwd: ecDir |
| }; |
| const extensionSrcDir = nodePath.resolve(ecDir, 'extension-src'); |
| const extensionESMDir = nodePath.resolve(ecDir, 'extension'); |
| |
| const typesDir = nodePath.resolve(ecDir, 'types'); |
| const esmDir = 'lib'; |
| |
| |
| const compileWorkList = [ |
| { |
| logLabel: 'main ts -> js-esm', |
| compilerOptionsOverride: { |
| module: 'ES2015', |
| rootDir: ecDir, |
| outDir: tmpDir, |
| // Generate types when buidling esm |
| declaration: true, |
| declarationDir: typesDir |
| }, |
| srcGlobby: mainSrcGlobby, |
| transformOptions: { |
| filesGlobby: {patterns: ['**/*.js'], cwd: tmpDir}, |
| preamble: preamble.js, |
| transformDEV: true |
| }, |
| before: async function () { |
| fsExtra.removeSync(tmpDir); |
| fsExtra.removeSync(nodePath.resolve(ecDir, 'types')); |
| fsExtra.removeSync(nodePath.resolve(ecDir, esmDir)); |
| fsExtra.removeSync(nodePath.resolve(ecDir, 'index.js')); |
| fsExtra.removeSync(nodePath.resolve(ecDir, 'index.blank.js')); |
| fsExtra.removeSync(nodePath.resolve(ecDir, 'index.common.js')); |
| fsExtra.removeSync(nodePath.resolve(ecDir, 'index.simple.js')); |
| }, |
| after: async function () { |
| fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.all.js'), nodePath.resolve(ecDir, 'index.js')); |
| fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.blank.js'), nodePath.resolve(ecDir, 'index.blank.js')); |
| fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.common.js'), nodePath.resolve(ecDir, 'index.common.js')); |
| fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.simple.js'), nodePath.resolve(ecDir, 'index.simple.js')); |
| fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, esmDir)); |
| |
| transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.js'), esmDir); |
| transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.blank.js'), esmDir); |
| transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.common.js'), esmDir); |
| transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.simple.js'), esmDir); |
| |
| await transformDistributionFiles(nodePath.resolve(ecDir, esmDir), esmDir); |
| await transformDistributionFiles(nodePath.resolve(ecDir, 'types'), esmDir); |
| fsExtra.removeSync(tmpDir); |
| } |
| }, |
| { |
| logLabel: 'extension ts -> js-esm', |
| compilerOptionsOverride: { |
| module: 'ES2015', |
| rootDir: extensionSrcDir, |
| outDir: extensionESMDir |
| }, |
| srcGlobby: extensionSrcGlobby, |
| transformOptions: { |
| filesGlobby: {patterns: ['**/*.js'], cwd: extensionESMDir}, |
| preamble: preamble.js, |
| transformDEV: true |
| }, |
| before: async function () { |
| fsExtra.removeSync(extensionESMDir); |
| }, |
| after: async function () { |
| await transformDistributionFiles(extensionESMDir, 'lib'); |
| } |
| } |
| ]; |
| |
| |
| |
| /** |
| * @public |
| */ |
| module.exports = async function () { |
| |
| for (let { |
| logLabel, compilerOptionsOverride, srcGlobby, |
| transformOptions, before, after |
| } of compileWorkList) { |
| |
| process.stdout.write(chalk.green.dim(`[${logLabel}]: compiling ...`)); |
| |
| before && await before(); |
| |
| let srcPathList = await readFilePaths(srcGlobby); |
| |
| await tsCompile(compilerOptionsOverride, srcPathList); |
| |
| process.stdout.write(chalk.green.dim(` done \n`)); |
| |
| process.stdout.write(chalk.green.dim(`[${logLabel}]: transforming ...`)); |
| |
| await transformCode(transformOptions); |
| |
| after && await after(); |
| |
| process.stdout.write(chalk.green.dim(` done \n`)); |
| } |
| |
| process.stdout.write(chalk.green.dim(`Generating entries ...`)); |
| generateEntries(); |
| process.stdout.write(chalk.green.dim(`Bundling DTS ...`)); |
| await bundleDTS(); |
| |
| console.log(chalk.green.dim('All done.')); |
| }; |
| |
| async function runTsCompile(localTs, compilerOptions, srcPathList) { |
| // Must do it. becuase the value in tsconfig.json might be different from the inner representation. |
| // For example: moduleResolution: "NODE" => moduleResolution: 2 |
| const {options, errors} = localTs.convertCompilerOptionsFromJson(compilerOptions, ecDir); |
| |
| if (errors.length) { |
| let errMsg = 'tsconfig parse failed: ' |
| + errors.map(error => error.messageText).join('. ') |
| + '\n compilerOptions: \n' + JSON.stringify(compilerOptions, null, 4); |
| assert(false, errMsg); |
| } |
| |
| // See: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API |
| |
| let program = localTs.createProgram(srcPathList, options); |
| let emitResult = program.emit(); |
| |
| let allDiagnostics = localTs |
| .getPreEmitDiagnostics(program) |
| .concat(emitResult.diagnostics); |
| |
| allDiagnostics.forEach(diagnostic => { |
| if (diagnostic.file) { |
| let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); |
| let message = localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); |
| console.log(chalk.red(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`)); |
| } |
| else { |
| console.log(chalk.red(localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n'))); |
| } |
| }); |
| if (allDiagnostics.length > 0) { |
| throw new Error('TypeScript Compile Failed') |
| } |
| } |
| module.exports.runTsCompile = runTsCompile; |
| |
| async function tsCompile(compilerOptionsOverride, srcPathList) { |
| assert( |
| compilerOptionsOverride |
| && compilerOptionsOverride.module |
| && compilerOptionsOverride.rootDir |
| && compilerOptionsOverride.outDir |
| ); |
| |
| let compilerOptions = { |
| ...tsConfig.compilerOptions, |
| ...compilerOptionsOverride, |
| sourceMap: false |
| }; |
| |
| runTsCompile(ts, compilerOptions, srcPathList); |
| } |
| |
| /** |
| * Transform import/require path in the entry file to `esm` or `lib`. |
| */ |
| function transformRootFolderInEntry(entryFile, replacement) { |
| let code = fs.readFileSync(entryFile, 'utf-8'); |
| // Simple regex replacement |
| // TODO More robust way? |
| assert( |
| !/(import\s+|from\s+|require\(\s*)["']\.\/echarts\./.test(code) |
| && !/(import\s+|from\s+|require\(\s*)["']echarts\./.test(code), |
| 'Import echarts.xxx.ts is not supported.' |
| ); |
| code = code.replace(/((import\s+|from\s+|require\(\s*)["'])\.\//g, `$1./${replacement}/`); |
| fs.writeFileSync( |
| entryFile, |
| // Also transform zrender. |
| singleTransformZRRootFolder(code, replacement), |
| 'utf-8' |
| ); |
| } |
| |
| /** |
| * Transform `zrender/src` to `zrender/esm` in all files |
| */ |
| async function transformDistributionFiles(rooltFolder, replacement) { |
| const files = await readFilePaths({ |
| patterns: ['**/*.js', '**/*.d.ts'], |
| cwd: rooltFolder |
| }); |
| // Simple regex replacement |
| // TODO More robust way? |
| for (let fileName of files) { |
| let code = fs.readFileSync(fileName, 'utf-8'); |
| code = singleTransformZRRootFolder(code, replacement); |
| // For lower ts version, not use import type |
| // TODO Use https://github.com/sandersn/downlevel-dts ? |
| // if (fileName.endsWith('.d.ts')) { |
| // code = singleTransformImportType(code); |
| // } |
| fs.writeFileSync(fileName, code, 'utf-8'); |
| } |
| } |
| |
| function singleTransformZRRootFolder(code, replacement) { |
| return code.replace(/([\"\'])zrender\/src\//g, `$1zrender/${replacement}/`); |
| } |
| |
| // function singleTransformImportType(code) { |
| // return code.replace(/import\s+type\s+/g, 'import '); |
| // } |
| |
| /** |
| * @param {Object} transformOptions |
| * @param {Object} transformOptions.filesGlobby {patterns: string[], cwd: string} |
| * @param {string} [transformOptions.preamble] See './preamble.js' |
| * @param {boolean} [transformOptions.transformDEV] |
| */ |
| async function transformCode({filesGlobby, preamble, transformDEV}) { |
| |
| let filePaths = await readFilePaths(filesGlobby); |
| |
| filePaths.map(filePath => { |
| let code = fs.readFileSync(filePath, 'utf8'); |
| |
| if (transformDEV) { |
| let result = transformDEVUtil.transform(code, false); |
| code = result.code; |
| } |
| |
| code = autoGeneratedFileAlert + code; |
| |
| if (preamble) { |
| code = preamble + code; |
| } |
| |
| fs.writeFileSync(filePath, code, 'utf8'); |
| }); |
| } |
| |
| async function readFilePaths({patterns, cwd}) { |
| assert(patterns && cwd); |
| return ( |
| await globby(patterns, {cwd}) |
| ).map( |
| srcPath => nodePath.resolve(cwd, srcPath) |
| ); |
| } |
| |
| async function bundleDTS() { |
| |
| const outDir = nodePath.resolve(__dirname, '../types/dist'); |
| const commonConfig = { |
| onwarn(warning, rollupWarn) { |
| // Not warn circular dependency |
| if (warning.code !== 'CIRCULAR_DEPENDENCY') { |
| rollupWarn(warning); |
| } |
| }, |
| plugins: [ |
| dts({ |
| respectExternal: true |
| }) |
| // { |
| // generateBundle(options, bundle) { |
| // for (let chunk of Object.values(bundle)) { |
| // chunk.code = ` |
| // type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; |
| // ${chunk.code}` |
| // } |
| // } |
| // } |
| ] |
| }; |
| |
| // Bundle chunks. |
| const parts = [ |
| 'core', 'charts', 'components', 'renderers', 'option' |
| ]; |
| const inputs = {}; |
| parts.forEach(partName => { |
| inputs[partName] = nodePath.resolve(__dirname, `../types/src/export/${partName}.d.ts`) |
| }); |
| |
| const bundle = await rollup.rollup({ |
| input: inputs, |
| ...commonConfig |
| }); |
| let idx = 1; |
| await bundle.write({ |
| dir: outDir, |
| minifyInternalExports: false, |
| manualChunks: (id) => { |
| // Only create one chunk. |
| return 'shared'; |
| }, |
| chunkFileNames: 'shared.d.ts' |
| }); |
| |
| // Bundle all in one |
| const bundleAllInOne = await rollup.rollup({ |
| input: nodePath.resolve(__dirname, `../types/src/export/all.d.ts`), |
| ...commonConfig |
| }); |
| await bundleAllInOne.write({ |
| file: nodePath.resolve(outDir, 'echarts.d.ts') |
| }); |
| } |
| |
| function readTSConfig() { |
| // tsconfig.json may have comment string, which is invalid if |
| // using `require('tsconfig.json'). So we use a loose parser. |
| let filePath = nodePath.resolve(ecDir, 'tsconfig.json'); |
| const tsConfigText = fs.readFileSync(filePath, {encoding: 'utf8'}); |
| return (new Function(`return ( ${tsConfigText} )`))(); |
| } |
| |
| |
| function generateEntries() { |
| ['charts', 'components', 'renderers', 'core'].forEach(entryName => { |
| if (entryName !== 'option') { |
| const jsCode = fs.readFileSync(nodePath.join(__dirname, `template/${entryName}.js`), 'utf-8'); |
| fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.js`), jsCode, 'utf-8'); |
| } |
| |
| const dtsCode = fs.readFileSync(nodePath.join(__dirname, `/template/${entryName}.d.ts`), 'utf-8'); |
| fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.d.ts`), dtsCode, 'utf-8'); |
| }); |
| } |
| |
| module.exports.readTSConfig = readTSConfig; |