462 lines
15 KiB
JavaScript
462 lines
15 KiB
JavaScript
/*
|
|
* 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;
|