You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
428 lines
14 KiB
428 lines
14 KiB
/* |
|
* 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 { transformImport } = require('zrender/build/transformImport'); |
|
|
|
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 transformLibFiles(nodePath.resolve(ecDir, esmDir), esmDir); |
|
await transformLibFiles(nodePath.resolve(ecDir, 'types'), esmDir); |
|
fsExtra.removeSync(tmpDir); |
|
} |
|
}, |
|
{ |
|
logLabel: 'extension ts -> js-esm', |
|
compilerOptionsOverride: { |
|
module: 'ES2015', |
|
declaration: false, |
|
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 transformLibFiles(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. |
|
singleTransformImport(code, replacement), |
|
'utf-8' |
|
); |
|
} |
|
|
|
/** |
|
* Transform `zrender/src` to `zrender/lib` in all files |
|
*/ |
|
async function transformLibFiles(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 = singleTransformImport(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'); |
|
} |
|
} |
|
|
|
/** |
|
* 1. Transform zrender/src to zrender/lib |
|
* 2. Add .js extensions |
|
*/ |
|
function singleTransformImport(code, replacement) { |
|
return transformImport( |
|
code.replace(/([\"\'])zrender\/src\//g, `$1zrender/${replacement}/`), |
|
(moduleName) => { |
|
// Ignore 'tslib' and 'echarts' in the extensions. |
|
if (moduleName === 'tslib' || moduleName === 'echarts') { |
|
return moduleName; |
|
} |
|
else if (moduleName === 'zrender/lib/export') { |
|
throw new Error('Should not import the whole zrender library.'); |
|
} |
|
else if (moduleName.endsWith('.ts')) { |
|
// Replace ts with js |
|
return moduleName.replace(/\.ts$/, '.js'); |
|
} |
|
else if (moduleName.endsWith('.js')) { |
|
return moduleName; |
|
} |
|
else { |
|
return moduleName + '.js' |
|
} |
|
} |
|
); |
|
} |
|
|
|
// 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', 'features' |
|
]; |
|
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', 'features'].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;
|
|
|