|
3 | 3 | const Command = require('egg-bin').Command;
|
4 | 4 | const path = require('path');
|
5 | 5 | const fs = require('fs');
|
| 6 | +const fsPromise = fs.promises; |
6 | 7 | const rimraf = require('mz-modules/rimraf');
|
7 | 8 | const fse = require('fs-extra');
|
8 | 9 | const globby = require('globby');
|
9 | 10 | const ncc = require('@midwayjs/ncc');
|
| 11 | +const typescript = require('typescript'); |
| 12 | +const terser = require('terser'); |
10 | 13 |
|
11 | 14 | const shebangRegEx = /^#![^\n\r]*[\r\n]/;
|
| 15 | +const inlineSourceMapRegEx = /\/\/# sourceMappingURL=data:application\/json;base64,(.*)/; |
12 | 16 |
|
13 | 17 | class BuildCommand extends Command {
|
14 | 18 | constructor(rawArgv) {
|
@@ -37,6 +41,9 @@ class BuildCommand extends Command {
|
37 | 41 | type: 'string',
|
38 | 42 | default: '',
|
39 | 43 | },
|
| 44 | + minify: { |
| 45 | + type: 'boolean', |
| 46 | + }, |
40 | 47 | mode: {
|
41 | 48 | description: 'bundle mode, "debug" or "release" (default)',
|
42 | 49 | type: 'string',
|
@@ -94,6 +101,10 @@ class BuildCommand extends Command {
|
94 | 101 | args.push(argv.project);
|
95 | 102 | }
|
96 | 103 | await this.helper.forkNode(tscCli, args, { cwd, execArgv: [] });
|
| 104 | + |
| 105 | + if (argv.minify) { |
| 106 | + await this.minify(tsConfig, outDirAbsolute); |
| 107 | + } |
97 | 108 | }
|
98 | 109 |
|
99 | 110 | async bundle(entry, outDir, { sourceMap = false, mode } = {}) {
|
@@ -207,6 +218,69 @@ class BuildCommand extends Command {
|
207 | 218 | return tsConfig.compilerOptions[optionKeyPath];
|
208 | 219 | }
|
209 | 220 | }
|
| 221 | + |
| 222 | + async minify(tsConfig, outDir) { |
| 223 | + const inlineSourceMap = !!tsConfig.compilerOptions.inlineSourceMap; |
| 224 | + const sourceMap = inlineSourceMap || tsConfig.compilerOptions.sourceMap; |
| 225 | + if (!sourceMap) { |
| 226 | + return; |
| 227 | + } |
| 228 | + |
| 229 | + let files; |
| 230 | + if (outDir) { |
| 231 | + files = globby.sync([ '**/*.js' ], { |
| 232 | + cwd: outDir, |
| 233 | + ignore: [ '**/node_modules' ], |
| 234 | + }); |
| 235 | + files = files.map(it => path.join(outDir, it)); |
| 236 | + } else { |
| 237 | + const host = typescript.createCompilerHost(tsConfig.compilerOptions); |
| 238 | + files = host.readDirectory(__dirname, [ '.ts' ], tsConfig.exclude, tsConfig.include); |
| 239 | + files = files.map(it => { |
| 240 | + return path.join(path.dirname(it), path.basename(it, '.js')); |
| 241 | + }); |
| 242 | + } |
| 243 | + |
| 244 | + for (const file of files) { |
| 245 | + let code = await fsPromise.readFile(file, 'utf8'); |
| 246 | + let map; |
| 247 | + if (inlineSourceMap) { |
| 248 | + map = this.parseInlineSourceMap(code); |
| 249 | + } else { |
| 250 | + map = await fsPromise.readFile(file + '.map', 'utf8'); |
| 251 | + } |
| 252 | + map = JSON.parse(map); |
| 253 | + const result = terser.minify(code, { |
| 254 | + compress: false, |
| 255 | + mangle: { |
| 256 | + keep_classnames: true, |
| 257 | + keep_fnames: true, |
| 258 | + }, |
| 259 | + sourceMap: { |
| 260 | + content: map, |
| 261 | + filename: path.basename(file), |
| 262 | + url: inlineSourceMap ? 'inline' : `${path.basename(file)}.map`, |
| 263 | + }, |
| 264 | + }); |
| 265 | + ({ code, map } = result); |
| 266 | + if (code == null) { |
| 267 | + break; |
| 268 | + } |
| 269 | + if (!inlineSourceMap) { |
| 270 | + await fsPromise.writeFile(file + '.map', map, 'utf8'); |
| 271 | + } |
| 272 | + await fsPromise.writeFile(file, code, 'utf8'); |
| 273 | + } |
| 274 | + } |
| 275 | + |
| 276 | + parseInlineSourceMap(code) { |
| 277 | + const match = inlineSourceMapRegEx.exec(code); |
| 278 | + if (match == null) { |
| 279 | + return; |
| 280 | + } |
| 281 | + const map = Buffer.from(match[1], 'base64').toString('utf8'); |
| 282 | + return map; |
| 283 | + } |
210 | 284 | }
|
211 | 285 |
|
212 | 286 | module.exports = BuildCommand;
|
0 commit comments