Spaces:
Running
Running
/* | |
* 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 previous 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.js'); | |
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 ssrClientGlobby = { | |
patterns: [ | |
'ssr/client/src/**/*.ts' | |
], | |
cwd: ecDir | |
}; | |
const ssrClientSrcDir = nodePath.resolve(ecDir, 'ssr/client/src'); | |
const ssrClientESMDir = nodePath.resolve(ecDir, 'ssr/client/lib'); | |
const ssrClientTypeDir = nodePath.resolve(ecDir, 'ssr/client/types'); | |
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 building 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'); | |
} | |
}, | |
{ | |
logLabel: 'ssr client ts -> js-esm', | |
compilerOptionsOverride: { | |
module: 'ES2015', | |
declaration: true, | |
rootDir: ssrClientSrcDir, | |
outDir: ssrClientESMDir, | |
declarationDir: ssrClientTypeDir | |
}, | |
srcGlobby: ssrClientGlobby, | |
transformOptions: { | |
filesGlobby: {patterns: ['**/*.js'], cwd: ssrClientESMDir}, | |
transformDEV: true | |
}, | |
before: async function () { | |
fsExtra.removeSync(ssrClientESMDir); | |
}, | |
after: async function () { | |
await transformLibFiles(ssrClientESMDir, '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, because 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) | |
); | |
} | |
// Bundle can be used in echarts-examples. | |
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', 'ssr/client/index'].forEach(entryPath => { | |
if (entryPath !== 'option') { | |
const jsCode = fs.readFileSync(nodePath.join(__dirname, `template/${entryPath}.js`), 'utf-8'); | |
fs.writeFileSync(nodePath.join(__dirname, `../${entryPath}.js`), jsCode, 'utf-8'); | |
} | |
// Make the d.ts in the same dir as .js, so that the can be found by tsc. | |
// package.json "types" in "exports" does not always seam to work. | |
const dtsCode = fs.readFileSync(nodePath.join(__dirname, `/template/${entryPath}.d.ts`), 'utf-8'); | |
fs.writeFileSync(nodePath.join(__dirname, `../${entryPath}.d.ts`), dtsCode, 'utf-8'); | |
}); | |
} | |
module.exports.readTSConfig = readTSConfig; | |