pre-publish.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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 prevoius 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 ecDir = nodePath.resolve(__dirname, '..');
  41. const tmpDir = nodePath.resolve(ecDir, 'pre-publish-tmp');
  42. const tsConfig = readTSConfig();
  43. const autoGeneratedFileAlert = `
  44. /**
  45. * AUTO-GENERATED FILE. DO NOT MODIFY.
  46. */
  47. `;
  48. const mainSrcGlobby = {
  49. patterns: [
  50. 'src/**/*.ts'
  51. ],
  52. cwd: ecDir
  53. };
  54. const extensionSrcGlobby = {
  55. patterns: [
  56. 'extension-src/**/*.ts'
  57. ],
  58. cwd: ecDir
  59. };
  60. const extensionSrcDir = nodePath.resolve(ecDir, 'extension-src');
  61. const extensionESMDir = nodePath.resolve(ecDir, 'extension');
  62. const typesDir = nodePath.resolve(ecDir, 'types');
  63. const esmDir = 'lib';
  64. const compileWorkList = [
  65. {
  66. logLabel: 'main ts -> js-esm',
  67. compilerOptionsOverride: {
  68. module: 'ES2015',
  69. rootDir: ecDir,
  70. outDir: tmpDir,
  71. // Generate types when buidling esm
  72. declaration: true,
  73. declarationDir: typesDir
  74. },
  75. srcGlobby: mainSrcGlobby,
  76. transformOptions: {
  77. filesGlobby: {patterns: ['**/*.js'], cwd: tmpDir},
  78. preamble: preamble.js,
  79. transformDEV: true
  80. },
  81. before: async function () {
  82. fsExtra.removeSync(tmpDir);
  83. fsExtra.removeSync(nodePath.resolve(ecDir, 'types'));
  84. fsExtra.removeSync(nodePath.resolve(ecDir, esmDir));
  85. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.js'));
  86. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.blank.js'));
  87. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.common.js'));
  88. fsExtra.removeSync(nodePath.resolve(ecDir, 'index.simple.js'));
  89. },
  90. after: async function () {
  91. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.all.js'), nodePath.resolve(ecDir, 'index.js'));
  92. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.blank.js'), nodePath.resolve(ecDir, 'index.blank.js'));
  93. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.common.js'), nodePath.resolve(ecDir, 'index.common.js'));
  94. fs.renameSync(nodePath.resolve(tmpDir, 'src/echarts.simple.js'), nodePath.resolve(ecDir, 'index.simple.js'));
  95. fs.renameSync(nodePath.resolve(tmpDir, 'src'), nodePath.resolve(ecDir, esmDir));
  96. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.js'), esmDir);
  97. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.blank.js'), esmDir);
  98. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.common.js'), esmDir);
  99. transformRootFolderInEntry(nodePath.resolve(ecDir, 'index.simple.js'), esmDir);
  100. await transformDistributionFiles(nodePath.resolve(ecDir, esmDir), esmDir);
  101. await transformDistributionFiles(nodePath.resolve(ecDir, 'types'), esmDir);
  102. fsExtra.removeSync(tmpDir);
  103. }
  104. },
  105. {
  106. logLabel: 'extension ts -> js-esm',
  107. compilerOptionsOverride: {
  108. module: 'ES2015',
  109. rootDir: extensionSrcDir,
  110. outDir: extensionESMDir
  111. },
  112. srcGlobby: extensionSrcGlobby,
  113. transformOptions: {
  114. filesGlobby: {patterns: ['**/*.js'], cwd: extensionESMDir},
  115. preamble: preamble.js,
  116. transformDEV: true
  117. },
  118. before: async function () {
  119. fsExtra.removeSync(extensionESMDir);
  120. },
  121. after: async function () {
  122. await transformDistributionFiles(extensionESMDir, 'lib');
  123. }
  124. }
  125. ];
  126. /**
  127. * @public
  128. */
  129. module.exports = async function () {
  130. for (let {
  131. logLabel, compilerOptionsOverride, srcGlobby,
  132. transformOptions, before, after
  133. } of compileWorkList) {
  134. process.stdout.write(chalk.green.dim(`[${logLabel}]: compiling ...`));
  135. before && await before();
  136. let srcPathList = await readFilePaths(srcGlobby);
  137. await tsCompile(compilerOptionsOverride, srcPathList);
  138. process.stdout.write(chalk.green.dim(` done \n`));
  139. process.stdout.write(chalk.green.dim(`[${logLabel}]: transforming ...`));
  140. await transformCode(transformOptions);
  141. after && await after();
  142. process.stdout.write(chalk.green.dim(` done \n`));
  143. }
  144. process.stdout.write(chalk.green.dim(`Generating entries ...`));
  145. generateEntries();
  146. process.stdout.write(chalk.green.dim(`Bundling DTS ...`));
  147. await bundleDTS();
  148. console.log(chalk.green.dim('All done.'));
  149. };
  150. async function runTsCompile(localTs, compilerOptions, srcPathList) {
  151. // Must do it. becuase the value in tsconfig.json might be different from the inner representation.
  152. // For example: moduleResolution: "NODE" => moduleResolution: 2
  153. const {options, errors} = localTs.convertCompilerOptionsFromJson(compilerOptions, ecDir);
  154. if (errors.length) {
  155. let errMsg = 'tsconfig parse failed: '
  156. + errors.map(error => error.messageText).join('. ')
  157. + '\n compilerOptions: \n' + JSON.stringify(compilerOptions, null, 4);
  158. assert(false, errMsg);
  159. }
  160. // See: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
  161. let program = localTs.createProgram(srcPathList, options);
  162. let emitResult = program.emit();
  163. let allDiagnostics = localTs
  164. .getPreEmitDiagnostics(program)
  165. .concat(emitResult.diagnostics);
  166. allDiagnostics.forEach(diagnostic => {
  167. if (diagnostic.file) {
  168. let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
  169. let message = localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
  170. console.log(chalk.red(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`));
  171. }
  172. else {
  173. console.log(chalk.red(localTs.flattenDiagnosticMessageText(diagnostic.messageText, '\n')));
  174. }
  175. });
  176. if (allDiagnostics.length > 0) {
  177. throw new Error('TypeScript Compile Failed')
  178. }
  179. }
  180. module.exports.runTsCompile = runTsCompile;
  181. async function tsCompile(compilerOptionsOverride, srcPathList) {
  182. assert(
  183. compilerOptionsOverride
  184. && compilerOptionsOverride.module
  185. && compilerOptionsOverride.rootDir
  186. && compilerOptionsOverride.outDir
  187. );
  188. let compilerOptions = {
  189. ...tsConfig.compilerOptions,
  190. ...compilerOptionsOverride,
  191. sourceMap: false
  192. };
  193. runTsCompile(ts, compilerOptions, srcPathList);
  194. }
  195. /**
  196. * Transform import/require path in the entry file to `esm` or `lib`.
  197. */
  198. function transformRootFolderInEntry(entryFile, replacement) {
  199. let code = fs.readFileSync(entryFile, 'utf-8');
  200. // Simple regex replacement
  201. // TODO More robust way?
  202. assert(
  203. !/(import\s+|from\s+|require\(\s*)["']\.\/echarts\./.test(code)
  204. && !/(import\s+|from\s+|require\(\s*)["']echarts\./.test(code),
  205. 'Import echarts.xxx.ts is not supported.'
  206. );
  207. code = code.replace(/((import\s+|from\s+|require\(\s*)["'])\.\//g, `$1./${replacement}/`);
  208. fs.writeFileSync(
  209. entryFile,
  210. // Also transform zrender.
  211. singleTransformZRRootFolder(code, replacement),
  212. 'utf-8'
  213. );
  214. }
  215. /**
  216. * Transform `zrender/src` to `zrender/esm` in all files
  217. */
  218. async function transformDistributionFiles(rooltFolder, replacement) {
  219. const files = await readFilePaths({
  220. patterns: ['**/*.js', '**/*.d.ts'],
  221. cwd: rooltFolder
  222. });
  223. // Simple regex replacement
  224. // TODO More robust way?
  225. for (let fileName of files) {
  226. let code = fs.readFileSync(fileName, 'utf-8');
  227. code = singleTransformZRRootFolder(code, replacement);
  228. // For lower ts version, not use import type
  229. // TODO Use https://github.com/sandersn/downlevel-dts ?
  230. // if (fileName.endsWith('.d.ts')) {
  231. // code = singleTransformImportType(code);
  232. // }
  233. fs.writeFileSync(fileName, code, 'utf-8');
  234. }
  235. }
  236. function singleTransformZRRootFolder(code, replacement) {
  237. return code.replace(/([\"\'])zrender\/src\//g, `$1zrender/${replacement}/`);
  238. }
  239. // function singleTransformImportType(code) {
  240. // return code.replace(/import\s+type\s+/g, 'import ');
  241. // }
  242. /**
  243. * @param {Object} transformOptions
  244. * @param {Object} transformOptions.filesGlobby {patterns: string[], cwd: string}
  245. * @param {string} [transformOptions.preamble] See './preamble.js'
  246. * @param {boolean} [transformOptions.transformDEV]
  247. */
  248. async function transformCode({filesGlobby, preamble, transformDEV}) {
  249. let filePaths = await readFilePaths(filesGlobby);
  250. filePaths.map(filePath => {
  251. let code = fs.readFileSync(filePath, 'utf8');
  252. if (transformDEV) {
  253. let result = transformDEVUtil.transform(code, false);
  254. code = result.code;
  255. }
  256. code = autoGeneratedFileAlert + code;
  257. if (preamble) {
  258. code = preamble + code;
  259. }
  260. fs.writeFileSync(filePath, code, 'utf8');
  261. });
  262. }
  263. async function readFilePaths({patterns, cwd}) {
  264. assert(patterns && cwd);
  265. return (
  266. await globby(patterns, {cwd})
  267. ).map(
  268. srcPath => nodePath.resolve(cwd, srcPath)
  269. );
  270. }
  271. async function bundleDTS() {
  272. const outDir = nodePath.resolve(__dirname, '../types/dist');
  273. const commonConfig = {
  274. onwarn(warning, rollupWarn) {
  275. // Not warn circular dependency
  276. if (warning.code !== 'CIRCULAR_DEPENDENCY') {
  277. rollupWarn(warning);
  278. }
  279. },
  280. plugins: [
  281. dts({
  282. respectExternal: true
  283. })
  284. // {
  285. // generateBundle(options, bundle) {
  286. // for (let chunk of Object.values(bundle)) {
  287. // chunk.code = `
  288. // type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
  289. // ${chunk.code}`
  290. // }
  291. // }
  292. // }
  293. ]
  294. };
  295. // Bundle chunks.
  296. const parts = [
  297. 'core', 'charts', 'components', 'renderers', 'option', 'features'
  298. ];
  299. const inputs = {};
  300. parts.forEach(partName => {
  301. inputs[partName] = nodePath.resolve(__dirname, `../types/src/export/${partName}.d.ts`)
  302. });
  303. const bundle = await rollup.rollup({
  304. input: inputs,
  305. ...commonConfig
  306. });
  307. let idx = 1;
  308. await bundle.write({
  309. dir: outDir,
  310. minifyInternalExports: false,
  311. manualChunks: (id) => {
  312. // Only create one chunk.
  313. return 'shared';
  314. },
  315. chunkFileNames: 'shared.d.ts'
  316. });
  317. // Bundle all in one
  318. const bundleAllInOne = await rollup.rollup({
  319. input: nodePath.resolve(__dirname, `../types/src/export/all.d.ts`),
  320. ...commonConfig
  321. });
  322. await bundleAllInOne.write({
  323. file: nodePath.resolve(outDir, 'echarts.d.ts')
  324. });
  325. }
  326. function readTSConfig() {
  327. // tsconfig.json may have comment string, which is invalid if
  328. // using `require('tsconfig.json'). So we use a loose parser.
  329. let filePath = nodePath.resolve(ecDir, 'tsconfig.json');
  330. const tsConfigText = fs.readFileSync(filePath, {encoding: 'utf8'});
  331. return (new Function(`return ( ${tsConfigText} )`))();
  332. }
  333. function generateEntries() {
  334. ['charts', 'components', 'renderers', 'core', 'features'].forEach(entryName => {
  335. if (entryName !== 'option') {
  336. const jsCode = fs.readFileSync(nodePath.join(__dirname, `template/${entryName}.js`), 'utf-8');
  337. fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.js`), jsCode, 'utf-8');
  338. }
  339. const dtsCode = fs.readFileSync(nodePath.join(__dirname, `/template/${entryName}.d.ts`), 'utf-8');
  340. fs.writeFileSync(nodePath.join(__dirname, `../${entryName}.d.ts`), dtsCode, 'utf-8');
  341. });
  342. }
  343. module.exports.readTSConfig = readTSConfig;