terser-webpack-plugin icon indicating copy to clipboard operation
terser-webpack-plugin copied to clipboard

Having nameCache controls

Open levani132 opened this issue 4 years ago • 14 comments

  • Operating System: Windows
  • Node Version: 13.5.0
  • NPM Version: 6.13.4
  • webpack Version: 4.41.2
  • terser-webpack-plugin Version: 3.0.2

Feature Proposal

What about adding nameCache option? I haven't used it before, but as documentation suggests we should be able to tell terser to use js object (or it would be more correct to call it dictionary) to map names (as I guess variables, classes, functions) to predefined, new, minified names instead of just renaming them to alphabetically smallest, nonempty, available string.

To say it simply, I'm asking to bring this somehow possible in webpack plugin too:

var cacheFileName = "/tmp/cache.json";
var options = {
    mangle: {
        properties: true,
    },
    nameCache: JSON.parse(fs.readFileSync(cacheFileName, "utf8"))
};
fs.writeFileSync("part1.js", Terser.minify({
    "file1.js": fs.readFileSync("file1.js", "utf8"),
    "file2.js": fs.readFileSync("file2.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync("part2.js", Terser.minify({
    "file3.js": fs.readFileSync("file3.js", "utf8"),
    "file4.js": fs.readFileSync("file4.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync(cacheFileName, JSON.stringify(options.nameCache), "utf8");

And this code in my opinion also saves new names cache to same file too. I think this is impossible now in terser webpack plugin.

Feature Request: Add ability/option to bind json file as name cache.

Feature Use Case

<! -- problem -->

Easiest use case would be, I need to build application (currently in angular) which has more than two files bundled (let's assume 3). First and second files both have dependency on third.

Now if I build only first file, after minification first minified file will have for example console.log(thirdModule.thirdModuleVariable) transformed into console.log(thirdModule.a).

But if I only build second file or if I build whole project thirdModule's thirdModuleVariable may become b.

</!-- problem -->

What I want in the end is that in angular application I have appModule => [firstLazyModule, secondLazyModule, thirdLazyModule]. Now I will build whole app, then I will comment out other two modules and build only appModule => [firstLazyModule] (because building all three of them takes too long and I have only changed code in firstLazyModule, after building app with only one module, I will take generated module bundle and I will replace corresponding module file in the firstly generated directory with this new module bundle file.

This whole thing works if I don't use terser plugin, but doesn't work with terser because of the problem I have provided. If I'll have nameCache file in the project, each time I build app it will generate and use in next builds same minified variable names and replacing modules will still work.

I hope we can work this out. Thank you in advance.

levani132 avatar Jun 02 '20 16:06 levani132

You can't use parallel in this case, so less perf

alexander-akait avatar Jun 02 '20 16:06 alexander-akait

Yeah, but maybe for someone parallel is more useful, but for me (And I think and hope there are many like me) this will be huge benefit. I mean will it be problem to make this possible without affecting everything else? Just add a little, simple option please.

levani132 avatar Jun 03 '20 07:06 levani132

@levani132 Yes, it is possible to implement, but you should do choose parallel or nameCache. Agree it can be useful for small applications.

alexander-akait avatar Jun 03 '20 11:06 alexander-akait

Yes, we won't be able to use both. Though I hope in my desired build flow this won't cause too much performance issues. My project has like 30 modules and I will be able to build them independently, so basically, if the build time becomes 12 times slower with my 12 threads pc, it will become 30 times faster to build separate modules.

By the way I haven't yet tested it but I have my own fork in my work PC where I'm playing around with this option and if I succeed I will bring that fork here on github and create a PR.

levani132 avatar Jun 03 '20 11:06 levani132

@levani132 Yes, feel free to send a PR

alexander-akait avatar Jun 03 '20 11:06 alexander-akait

hm, nameCache already work, just set parallel: false, but webpack@4 adding wrapper to mangle top function/vars/etc impossible, but it is possible with webpack@5

alexander-akait avatar Jul 16 '20 14:07 alexander-akait

Yeah, I see, but I can't wait until webpack 5 will be finally released (Also I'm trying to use nameCache in angular, so I will have to wait for angular to add webpack 5). So I managed to do what I wanted to do in webpack 4, but there's a little problem, Angular also uses optimization flag which converts some variables. If I disable Angular optimizations I achieve what I want (having cached everything that is mangled) but bundle size increases about 2-3 times. So now I'm researching what angular's optimization flag does to somehow make it cache things too maybe. After I succeed I'll test the functionality and send PR here too.

levani132 avatar Jul 17 '20 09:07 levani132

https://github.com/webpack-contrib/terser-webpack-plugin/blob/c5a9d4146c04ae51e016a7a3950d7e6e761c6cf6/src/utils.js#L298

In order for parallel and nameCache to be used together, we can update nameCache every time we call terser's minify, and then save the nameCache to a file after the webpack is packaged, and everything will work fine the second time.

masx200 avatar Aug 23 '22 13:08 masx200

But this leads to the cost that the modified code may need to be packaged twice to work properly.

masx200 avatar Aug 23 '22 13:08 masx200

https://github.com/TrySound/rollup-plugin-terser/blob/6b50b7fadbe8812d13452def82c36133f58581fd/rollup-plugin-terser.js#L56

https://github.com/TrySound/rollup-plugin-terser/blob/6b50b7fadbe8812d13452def82c36133f58581fd/transform.js#L5

masx200 avatar Aug 23 '22 14:08 masx200

If you want namecache and parallel to be used together and you don't want to package twice after each change, you can only make namecache a concurrenthashmap.

masx200 avatar Aug 23 '22 14:08 masx200

@masx200 yeah, you can't use nameCache and parallel together currently, but thank you for openning an issue in terser repo

alexander-akait avatar Aug 29 '22 13:08 alexander-akait

IMHO, opening an issue in the terser repo would not help. First of all, isn't the result of build processing should be deterministic for long-time caching? If we want to use concurrency here, race conditions must occur somehow. Even if we manage to make the namecache concurrency safe, which one gets to write the map and occupy some name first is still unpredictable. @masx200

it is possible to implement, but you should do choose parallel or nameCache. Agree it can be useful for small applications.

For big web apps, mangle properties could reduce the bundles' size magnificently, like what google compile closure does for Google Docs. However, this requires all related files to be processed by Terser all at once, which requires us to turn off parallel. It is also intolerable for big apps.

More build time vs Smaller bundles' size.

Avoid using a module bundler, as they usually will call Terser on each file individually, making it impossible to pass mangled objects between modules.

Is there any way, like utilizing the module graph to find out which bundles can be processed simultaneously without causing naming conflicts? @alexander-akait

icy0307 avatar Feb 01 '23 17:02 icy0307

I don't think we really can solve it here... Files are in different threads, something like salt on terser side can solve it - i.e. we can generate salt using path.relative(context, filepath) and generate salt for file, then terser will shift all names based on salt (it is theoretical idea), so it will work in parallel mode

alexander-akait avatar Feb 10 '23 19:02 alexander-akait