pre-publish.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * [Create CommonJS files]:
  21. * Compatible with previous folder structure: `echarts/lib` exists in `node_modules`
  22. * (1) Build all files to CommonJS to `echarts/lib`.
  23. * (2) Remove __DEV__.
  24. * (3) Mount `echarts/src/export.js` to `echarts/lib/echarts.js`.
  25. *
  26. * [Create ESModule files]:
  27. * Build all files to CommonJS to `echarts/esm`.
  28. */
  29. const nodePath = require('path');
  30. const assert = require('assert');
  31. const fs = require('fs');
  32. const fsExtra = require('fs-extra');
  33. const chalk = require('chalk');
  34. const ts = require('typescript');
  35. const globby = require('globby');
  36. const transformDEVUtil = require('./transform-dev');
  37. const preamble = require('./preamble');
  38. const dts = require('@lang/rollup-plugin-dts').default;
  39. const rollup = require('rollup');
  40. const { transformImport } = require('zrender/build/transformImport.js');
  41. const ecDir = nodePath.resolve(__dirname, '..');
  42. const tmpDir = nodePath.resolve(ecDir, 'pre-publish-tmp');
  43. const tsConfig = readTSConfig();
  44. const autoGeneratedFileAlert = `
  45. /**
  46. * AUTO-GENERATED FILE. DO NOT MODIFY.
  47. */
  48. `;
  49. const mainSrcGlobby = {
  50. patterns: [
  51. 'src/**/*.ts'
  52. ],
  53. cwd: ecDir
  54. };
  55. const extensionSrcGlobby = {
  56. patterns: [
  57. 'extension-src/**/*.ts'
  58. ],
  59. cwd: ecDir
  60. };
  61. const extensionSrcDir = nodePath.resolve(ecDir, 'extension-src');
  62. const extensionESMDir = nodePath.resolve(ecDir, 'extension');
  63. const ssrClientGlobby = {
  64. patterns: [
  65. 'ssr/client/src/**/*.ts'
  66. ],
  67. cwd: ecDir
  68. };
  69. const ssrClientSrcDir = nodePath.resolve(ecDir, 'ssr/client/src');
  70. const ssrClientESMDir = nodePath.resolve(ecDir, 'ssr/client/lib');
  71. const ssrClientTypeDir = nodePath.resolve(ecDir, 'ssr/client/types');
  72. const typesDir = nodePath.resolve(ecDir, 'types');
  73. const esmDir = 'lib';
  74. const compileWorkList = [
  75. {
  76. logLabel: 'main ts -> js-esm',
  77. compilerOptionsOverride: {
  78. module: 'ES2015',
  79. rootDir: ecDir,
  80. outDir: tmpDir,
  81. // Generate types when building esm
  82. declaration: true,
  83. declarationDir: typesDir
  84. },
  85. srcGlobby: mainSrcGlobby,
  86. transformOptions: {
  87. filesGlobby: {patterns: ['**/*.js'], cwd: tmpDir},
  88. preamble: preamble.js,
  89. transformDEV: true
  90. },
  91. before: async function () {
  92. fsExtra.removeSync(tmpDir);
  93. fsExtra.removeSync(nodePath.resolve(ecDir, 'types'));
  94. fsExtra.removeSync(nodePath.resolve(ecDir, esmDir));
  95. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.js'));
  96. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.blank.js'));
  97. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.common.js'));
  98. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.simple.js'));
  99. },
  100. after: async function () {
  101. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.all.js'), nodePath.resolve(ecDir, 'index.js'));
  102. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.blank.js'), nodePath.resolve(ecDir, 'index.blank.js'));
  103. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.common.js'), nodePath.resolve(ecDir, 'index.common.js'));
  104. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.simple.js'), nodePath.resolve(ecDir, 'index.simple.js'));
  105. fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, esmDir));
  106. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.js'), esmDir);
  107. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.blank.js'), esmDir);
  108. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.common.js'), esmDir);
  109. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.simple.js'), esmDir);
  110. await transformLibFiles(nodePath.resolve(ecDir, esmDir), esmDir);
  111. await transformLibFiles(nodePath.resolve(ecDir, 'types'), esmDir);
  112. fsExtra.removeSync(tmpDir);
  113. }
  114. },
  115. {
  116. logLabel: 'extension ts -> js-esm',
  117. compilerOptionsOverride: {
  118. module: 'ES2015',
  119. declaration: false,
  120. rootDir: extensionSrcDir,
  121. outDir: extensionESMDir
  122. },
  123. srcGlobby: extensionSrcGlobby,
  124. transformOptions: {
  125. filesGlobby: {patterns: ['**/*.js'], cwd: extensionESMDir},
  126. preamble: preamble.js,
  127. transformDEV: true
  128. },
  129. before: async function () {
  130. fsExtra.removeSync(extensionESMDir);
  131. },
  132. after: async function () {
  133. await transformLibFiles(extensionESMDir, 'lib');
  134. }
  135. },
  136. {
  137. logLabel: 'ssr client ts -> js-esm',
  138. compilerOptionsOverride: {
  139. module: 'ES2015',
  140. declaration: true,
  141. rootDir: ssrClientSrcDir,
  142. outDir: ssrClientESMDir,
  143. declarationDir: ssrClientTypeDir
  144. },
  145. srcGlobby: ssrClientGlobby,
  146. transformOptions: {
  147. filesGlobby: {patterns: ['**/*.js'], cwd: ssrClientESMDir},
  148. transformDEV: true
  149. },
  150. before: async function () {
  151. fsExtra.removeSync(ssrClientESMDir);
  152. },
  153. after: async function () {
  154. await transformLibFiles(ssrClientESMDir, 'lib');
  155. }
  156. }
  157. ];
  158. /**
  159. * @public
  160. */
  161. module.exports = async function () {
  162. for (let {
  163. logLabel, compilerOptionsOverride, srcGlobby,
  164. transformOptions, before, after
  165. } of compileWorkList) {
  166. process.stdout.write(chalk.green.dim(`[${logLabel}]: compiling ...`));
  167. before && await before();
  168. let srcPathList = await readFilePaths(srcGlobby);
  169. await tsCompile(compilerOptionsOverride, srcPathList);
  170. process.stdout.write(chalk.green.dim(` done \n`));
  171. process.stdout.write(chalk.green.dim(`[${logLabel}]: transforming ...`));
  172. await transformCode(transformOptions);
  173. after && await after();
  174. process.stdout.write(chalk.green.dim(` done \n`));
  175. }
  176. process.stdout.write(chalk.green.dim(`Generating entries ...`));
  177. generateEntries();
  178. process.stdout.write(chalk.green.dim(`Bundling DTS ...`));
  179. await bundleDTS();
  180. console.log(chalk.green.dim('All done.'));
  181. };
  182. async function runTsCompile(localTs, compilerOptions, srcPathList) {
  183. // Must do it, because the value in tsconfig.json might be different from the inner representation.
  184. // For example: moduleResolution: "NODE" => moduleResolution: 2
  185. const {options, errors} = localTs.convertCompilerOptionsFromJson(compilerOptions, ecDir);
  186. if (errors.length) {
  187. let errMsg = 'tsconfig parse failed: '
  188. + errors.map(error => error.messageText).join('. ')
  189. + '\n compilerOptions: \n' + JSON.stringify(compilerOptions, null, 4);
  190. assert(false, errMsg);
  191. }
  192. // See: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
  193. let program = localTs.createProgram(srcPathList, options);
  194. let emitResult = program.emit();
  195. let allDiagnostics = localTs
  196. .getPreEmitDiagnostics(program)
  197. .concat(emitResult.diagnostics);
  198. allDiagnostics.forEach(diagnostic => {
  199. if (diagnostic.file) {
  200. let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
  201. let message = localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
  202. console.log(chalk.red(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`));
  203. }
  204. else {
  205. console.log(chalk.red(localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n')));
  206. }
  207. });
  208. if (allDiagnostics.length > 0) {
  209. throw new Error('TypeScript Compile Failed')
  210. }
  211. }
  212. module.exports.runTsCompile = runTsCompile;
  213. async function tsCompile(compilerOptionsOverride, srcPathList) {
  214. assert(
  215. compilerOptionsOverride
  216. && compilerOptionsOverride.module
  217. && compilerOptionsOverride.rootDir
  218. && compilerOptionsOverride.outDir
  219. );
  220. let compilerOptions = {
  221. ...tsConfig.compilerOptions,
  222. ...compilerOptionsOverride,
  223. sourceMap: false
  224. };
  225. runTsCompile(ts, compilerOptions, srcPathList);
  226. }
  227. /**
  228. * Transform import/require path in the entry file to `esm` or `lib`.
  229. */
  230. function transformRootFolderInEntry(entryFile, replacement) {
  231. let code = fs.readFileSync(entryFile, 'utf-8');
  232. // Simple regex replacement
  233. // TODO More robust way?
  234. assert(
  235. !/(import\s+|from\s+|require\(\s*)["']\.\/echarts\./.test(code)
  236. && !/(import\s+|from\s+|require\(\s*)["']echarts\./.test(code),
  237. 'Import echarts.xxx.ts is not supported.'
  238. );
  239. code = code.replace(/((import\s+|from\s+|require\(\s*)["'])\.\//g, `$1./${replacement}/`);
  240. fs.writeFileSync(
  241. entryFile,
  242. // Also transform zrender.
  243. singleTransformImport(code, replacement),
  244. 'utf-8'
  245. );
  246. }
  247. /**
  248. * Transform `zrender/src` to `zrender/lib` in all files
  249. */
  250. async function transformLibFiles(rooltFolder, replacement) {
  251. const files = await readFilePaths({
  252. patterns: ['**/*.js', '**/*.d.ts'],
  253. cwd: rooltFolder
  254. });
  255. // Simple regex replacement
  256. // TODO More robust way?
  257. for (let fileName of files) {
  258. let code = fs.readFileSync(fileName, 'utf-8');
  259. code = singleTransformImport(code, replacement);
  260. // For lower ts version, not use import type
  261. // TODO Use https://github.com/sandersn/downlevel-dts ?
  262. // if (fileName.endsWith('.d.ts')) {
  263. // code = singleTransformImportType(code);
  264. // }
  265. fs.writeFileSync(fileName, code, 'utf-8');
  266. }
  267. }
  268. /**
  269. * 1. Transform zrender/src to zrender/lib
  270. * 2. Add .js extensions
  271. */
  272. function singleTransformImport(code, replacement) {
  273. return transformImport(
  274. code.replace(/([\"\'])zrender\/src\//g, `$1zrender/${replacement}/`),
  275. (moduleName) => {
  276. // Ignore 'tslib' and 'echarts' in the extensions.
  277. if (moduleName === 'tslib' || moduleName === 'echarts') {
  278. return moduleName;
  279. }
  280. else if (moduleName === 'zrender/lib/export') {
  281. throw new Error('Should not import the whole zrender library.');
  282. }
  283. else if (moduleName.endsWith('.ts')) {
  284. // Replace ts with js
  285. return moduleName.replace(/\.ts$/, '.js');
  286. }
  287. else if (moduleName.endsWith('.js')) {
  288. return moduleName;
  289. }
  290. else {
  291. return moduleName + '.js'
  292. }
  293. }
  294. );
  295. }
  296. // function singleTransformImportType(code) {
  297. // return code.replace(/import\s+type\s+/g, 'import ');
  298. // }
  299. /**
  300. * @param {Object} transformOptions
  301. * @param {Object} transformOptions.filesGlobby {patterns: string[], cwd: string}
  302. * @param {string} [transformOptions.preamble] See './preamble.js'
  303. * @param {boolean} [transformOptions.transformDEV]
  304. */
  305. async function transformCode({filesGlobby, preamble, transformDEV}) {
  306. let filePaths = await readFilePaths(filesGlobby);
  307. filePaths.map(filePath => {
  308. let code = fs.readFileSync(filePath, 'utf8');
  309. if (transformDEV) {
  310. let result = transformDEVUtil.transform(code, false);
  311. code = result.code;
  312. }
  313. code = autoGeneratedFileAlert + code;
  314. if (preamble) {
  315. code = preamble + code;
  316. }
  317. fs.writeFileSync(filePath, code, 'utf8');
  318. });
  319. }
  320. async function readFilePaths({patterns, cwd}) {
  321. assert(patterns && cwd);
  322. return (
  323. await globby(patterns, {cwd})
  324. ).map(
  325. srcPath => nodePath.resolve(cwd, srcPath)
  326. );
  327. }
  328. // Bundle can be used in echarts-examples.
  329. async function bundleDTS() {
  330. const outDir = nodePath.resolve(__dirname, '../types/dist');
  331. const commonConfig = {
  332. onwarn(warning, rollupWarn) {
  333. // Not warn circular dependency
  334. if (warning.code !== 'CIRCULAR_DEPENDENCY') {
  335. rollupWarn(warning);
  336. }
  337. },
  338. plugins: [
  339. dts({
  340. respectExternal: true
  341. })
  342. // {
  343. // generateBundle(options, bundle) {
  344. // for (let chunk of Object.values(bundle)) {
  345. // chunk.code = `
  346. // type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
  347. // ${chunk.code}`
  348. // }
  349. // }
  350. // }
  351. ]
  352. };
  353. // Bundle chunks.
  354. const parts = [
  355. 'core', 'charts', 'components', 'renderers', 'option', 'features'
  356. ];
  357. const inputs = {};
  358. parts.forEach(partName => {
  359. inputs[partName] = nodePath.resolve(__dirname, `../types/src/export/${partName}.d.ts`)
  360. });
  361. const bundle = await rollup.rollup({
  362. input: inputs,
  363. ...commonConfig
  364. });
  365. let idx = 1;
  366. await bundle.write({
  367. dir: outDir,
  368. minifyInternalExports: false,
  369. manualChunks: (id) => {
  370. // Only create one chunk.
  371. return 'shared';
  372. },
  373. chunkFileNames: 'shared.d.ts'
  374. });
  375. // Bundle all in one
  376. const bundleAllInOne = await rollup.rollup({
  377. input: nodePath.resolve(__dirname, `../types/src/export/all.d.ts`),
  378. ...commonConfig
  379. });
  380. await bundleAllInOne.write({
  381. file: nodePath.resolve(outDir, 'echarts.d.ts')
  382. });
  383. }
  384. function readTSConfig() {
  385. // tsconfig.json may have comment string, which is invalid if
  386. // using `require('tsconfig.json'). So we use a loose parser.
  387. let filePath = nodePath.resolve(ecDir, 'tsconfig.json');
  388. const tsConfigText = fs.readFileSync(filePath, {encoding: 'utf8'});
  389. return (new Function(`return ( ${tsConfigText} )`))();
  390. }
  391. function generateEntries() {
  392. ['charts', 'components', 'renderers', 'core', 'features', 'ssr/client/index'].forEach(entryPath => {
  393. if (entryPath !== 'option') {
  394. const jsCode = fs.readFileSync(nodePath.join(__dirname, `template/${entryPath}.js`), 'utf-8');
  395. fs.writeFileSync(nodePath.join(__dirname, `../${entryPath}.js`), jsCode, 'utf-8');
  396. }
  397. // Make the d.ts in the same dir as .js, so that the can be found by tsc.
  398. // package.json "types" in "exports" does not always seam to work.
  399. const dtsCode = fs.readFileSync(nodePath.join(__dirname, `/template/${entryPath}.d.ts`), 'utf-8');
  400. fs.writeFileSync(nodePath.join(__dirname, `../${entryPath}.d.ts`), dtsCode, 'utf-8');
  401. });
  402. }
  403. module.exports.readTSConfig = readTSConfig;