refactor(web): refactored unused helper detection aspect

This commit is contained in:
Joshua A. Horton 2023-06-07 16:11:58 +07:00
parent b66c173fc2
commit 8deddfbab2

View file

@ -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);