Webpack in Visual Studio for large solutions

    CPAPWe have 51 projects in solution. 10 of them use TypeScript. The amount of minimized JavaScript code is ~ 1 MB. TypeScript code of some projects depends on the code of other projects. Many React components use global variables.


    Together, this leads to long hours of debugging front-end code. To simplify our life, we introduced Webpack. And along the way we caught a rake.


    TL; DR


    1. Install node 7 + npm
    2. We execute in the console npm i -g webpack typescript
    3. Install Webpack Task Runner
    4. Add webpack.config.js
      to the folder of the "main" project
    5. Add webpack.config.part.js
      to the folder of each dependent project

    The most common problems that we encounter when working with TypeScript code are “inconsistent” inheritance and an abundance of global variables. They appear with the default settings of the studio and a large size of the solution.


    The problem is "inconsistent" inheritance


    The inheritance problem occurs when the base class is connected after the child. When an error falls in our application Uncaught TypeError: Cannot read property 'prototype' of undefined, this is most likely a problem with "inconsistent" inheritance.


    I will give an example. At the "start" we wrote the code below.


    namespace Sandbox {
        export class Base {
            protected foo() {
                console.log('foo');
            }
        }
        export class Derived extends Base {
            public baz() {
                this.foo();
                console.log('baz');
            }
        }
    }

    We want each class to be in a separate file, because we think it’s more convenient. And we divided the code from the listing above into two files: base.tsand derived.ts.


    namespace Sandbox {
        export class Base {
            protected foo() {
                console.log('foo');
            }
        }
    }

    namespace Sandbox {
        export class Derived extends Base {
            public baz() {
                this.foo();
                console.log('baz');
            }
        }
    }

    A file has appeared derived.tsthat is implicitly dependent on the file base.ts. Now the order of connecting these two files is important: if you connect first derived.js, and then base.jswe get an error message.


    When setting the studio "by default", the order of connecting dependent scripts is important.


    The problem is global variables


    In order for TypeScript code from different projects to use common code, you have to declare global variables.


    For example, we wrote a useful function makeSandwichin the namespace Utilsin the Administration project:


    namespace Utils {
        ...
        export function makeSandwich() {
            ...
        }
    }

    We get the following code in JavaScript:


    var Utils;
    (function (Utils) {
        function makeSandwich() {
            ...
        }
        Utils.makeSandwich = makeSandwich;
    })(Utils || (Utils = {}));

    A global variable has been created Utils. In order to call a function, you have to access the global variable.


    Global variables themselves are not harmful, but their misuse leads to long hours of debugging. For example, you can overwrite or overlap them.


    At studio settings "by default" global variables are created.


    Decision


    And "inconsistent" inheritance and global variables are problems of dependency resolution. In the JavaScript world, dependencies resolve special tools: Webpack, Browserify, RequireJS, SystemJS, and others. I chose Webpack, since I used to work with it.


    In Visual Studio, starting with version 13, Task Runner appeared. This is such a tool that binds the task to the moment of the project life cycle: at opening, on Clean, before the build or after the build. Extend Task Runner plugins for the studio.


    To embed Webpack in a build studio, use the Task Runner and the Webpack Task Runner plugin . It’s easy to work with them. Create a file webpack.config.jsin the folder with the project and hang the "Watch - Development" task to open the project. More details in this article .


    Each time "Watch - Development" is launched, watch is launched over the file system - a build is triggered for every change in the observed files. Without additional settings, everything works fine if you have one project. If you have two or more projects, watch will be launched for each of them. We have 10 projects with TypeScript. 10 minutes on my computer work 3 minutes on the build scripts. And this is when changing one ts file. Need to improve.


    Our projects are arranged in such a way that there is a "main" project with a website framework and "dependent" projects with plugins.


    I configured our build in such a way that when loading the "main" project, the "Watch - Development" task will be launched using the studio Task Runner. The basic settings are webpack.config.js: loaders, tsconfig, plugins. The webpack.config.jscode is also written inside , which searches for files in all folders of the solution webpack.config.part.js. Each file webpack.config.part.jscontains the build settings for a specific project: entry, tsconfig settings overrides and more. Usually, there is only an entry. Both the "main" and "dependent" projects contain a file webpack.config.part.js. Thus, for build scripts in all projects, only one watch is used.


    Setting up a build server is commonplace: launch it webpack -pin the folder of the "main" project.


    Another caveat - in addition to Webpack, the studio is still building our TypeScript. In TypeScript from version 2.0 in tsconfig.json, you can disable file builds excludewith a directive with a pattern ./Scripts/*. Webpack will ignore this setting. But in TypeScript 2.1, the compiler must find at least one file for the build, otherwise the error will fail. I had to leave one empty file for sacrificing the studio.


    results


    And inheritance and use of global variables degenerated into import directives:


    import { Base } from '../../../../../BaseProject/Scripts/Base';
    import * as Utils from '../../../../../Utils';
    export class Derived extends Base {
        public baz() {
            this.foo();
            Utils.makeSandwich();
            console.log('baz');
        }
    }

    Pay attention to the long relative paths. They are here on purpose to show that this is happening. Use intermediate files with imports from other projects and “encapsulate” a ton of long relative paths.


    When using one watch instead of several, the build accelerated from 3 minutes to several seconds.


    The compilation product will be a single file containing all the imported files. Moreover, when you start with production-settings, the file will be minimized. You can learn more about Webpack at webpack.js.org .


    It is not possible to rewrite all our code for using imports in one approach. But the method described in the article allows you to do this work in parts.


    conclusions


    • You have one project - everything works this way, do not touch
    • You have one project, but you want Webpack buns - you are here
    • You have few projects (2-5) - you are still here
    • You have more than 5 projects - try the method described in the article

    References



    Also popular now: