UNPKG

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