diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index bc0ea61695..06024965b4 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -29,103 +29,87 @@ if(process.argv.length > 2) { } } -// Component #1: Detect all `tslib` helpers we actually want to use. -const tslibHelperNames = [ - "__extends", - "__assign", - "__rest", - "__decorate", - "__param", - "__metadata", - "__awaiter", - "__generator", - "__exportStar", - "__createBinding", - "__values", - "__read", - "__spread", - "__spreadArrays", - "__await", - "__asyncGenerator", - "__asyncDelegator", - "__asyncValues", - "__makeTemplateObject", - "__importStar", - "__importDefault", - "__classPrivateFieldGet", - "__classPrivateFieldSet" -]; +async function determineNeededDowncompileHelpers(config, log) { + // Component #1: Detect all `tslib` helpers we actually want to use. + const tslibHelperNames = [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArrays", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet" + ]; -const detectedHelpers = []; + const detectedHelpers = []; -let tslibHelperDetectionPlugin = { - name: 'tslib helper use detection', - setup(build) { - build.onLoad({filter: /\.js$/}, async (args) => { - // - if(/tslib.js$/.test(args.path)) { - return; - } - - let source = await fs.promises.readFile(args.path, 'utf8'); - - for(let helper of tslibHelperNames) { - if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { - detectedHelpers.push(helper); + let tslibHelperDetectionPlugin = { + name: 'tslib helper use detection', + setup(build) { + build.onLoad({filter: /\.js$/}, async (args) => { + // + if(/tslib.js$/.test(args.path)) { + return; } - } - return; - }); - } -} -// + let source = await fs.promises.readFile(args.path, 'utf8'); -// Component #2: when we've actually determined which ones are safe to remove, this plugin -// can remove their code. -let tslibForcedTreeshakingPlugin = { - name: 'tslib helpers - forced treeshaking', - setup(build) { - build.onLoad({filter: /tslib.js$/}, async (args) => { - let source = await fs.promises.readFile(args.path, 'utf8'); - - // TODO: transformations to eliminate the stuff we don't want. - for(let unusedHelper of unusedHelpers) { - // Removes the 'exporter' line used to actually export it from the tslib source. - source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); - - // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. - let definitionStart = source.indexOf(`${unusedHelper} = function`); - if(definitionStart == -1) { - console.error("tslib has likely been updated recently; could not erase definition for helper " + unusedHelper); - continue; - } - let scopeDepth = 0; - let i = definitionStart; - let char = source.charAt(i); - while(char != '}' || --scopeDepth != 0) { - if(char == '{') { - scopeDepth++; + for(let helper of tslibHelperNames) { + if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { + detectedHelpers.push(helper); } - i++; - char = source.charAt(i); } - i++; // we want to erase it, too. - source = source.replace(source.substring(definitionStart, i), ''); - - // The top-level var declaration is auto-removed by esbuild when no references to it remain. - - } - - return { - contents: source, - loader: 'js' - }; - }); + return; + }); + } } + + // tslib tree-shake phase 1 - detecting which helpers are safe to remove. + await esbuild.build({ + ...config, + plugins: [ tslibHelperDetectionPlugin, ...config.plugins ], + write: false + }); + + // At this point, we can determine what's unused. + detectedHelpers.sort(); + + const unusedHelpers = []; + tslibHelperNames.forEach((entry) => { + if(!detectedHelpers.find((detected) => detected == entry)) { + unusedHelpers.push(entry); + } + }); + + if(log) { + // Logs on the tree-shaking decisions + console.log("Detected helpers from tslib: "); + console.log(); + + console.log(); + console.log("Unused helpers: "); + console.log(unusedHelpers); + } + + return unusedHelpers; } -// /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - @@ -160,32 +144,66 @@ const commonConfig = { 'index': '../../../build/app/browser/obj/debug-main.js', }, outfile: '../../../build/app/browser/debug/keymanweb.js', - plugins: [ tslibForcedTreeshakingPlugin, es5ClassAnnotationAsPurePlugin ], + plugins: [ es5ClassAnnotationAsPurePlugin ], target: "es5", treeShaking: true, tsconfig: './tsconfig.json' }; // tslib tree-shake phase 1 - detecting which helpers are safe to remove. -await esbuild.build({ - ...commonConfig, - plugins: [ tslibHelperDetectionPlugin ], - write: false -}); +const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig); -// Logs on the tree-shaking decisions -console.log("Detected helpers from tslib: "); -console.log(detectedHelpers.sort()); +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +let tslibForcedTreeshakingPlugin = { + name: 'tslib helpers - forced treeshaking', + setup(build) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); -console.log(); -console.log("Unused helpers: "); -const unusedHelpers = []; -tslibHelperNames.forEach((entry) => { - if(!detectedHelpers.find((detected) => detected == entry)) { - unusedHelpers.push(entry); + // TODO: transformations to eliminate the stuff we don't want. + for(let unusedHelper of unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let definitionStart = source.indexOf(`${unusedHelper} = function`); + if(definitionStart == -1) { + if(unusedHelper == '__createBinding') { + console.warn("Currently unable to force-treeshake the __createBinding tslib helper"); + } else { + console.error("tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper); + } + continue; + } + + let scopeDepth = 0; + let i = definitionStart; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0) { + if(char == '{') { + scopeDepth++; + } + i++; + char = source.charAt(i); + } + i++; // we want to erase it, too. + + source = source.replace(source.substring(definitionStart, i), ''); + + // The top-level var declaration is auto-removed by esbuild when no references to it remain. + + } + + return { + contents: source, + loader: 'js' + }; + }); } -}); -console.log(unusedHelpers); +} + +commonConfig.plugins = [tslibForcedTreeshakingPlugin, ...commonConfig.plugins]; // From here, the builds are configured to do phase 2 from the preprocessing data done 'til now. await esbuild.build(commonConfig);