Skip to content

Multiple TS config/projects, entry files, and shared code #647

Closed

Description

@OliverJAsh

I have 3 dirs:

- service-worker
- browser
- shared

service-worker and browser have their own tsconfig.json. This is necessary because I need to specify a different lib compiler option to files in these projects, as they are ran in different environments (service workers and DOM/window/browser, respectively).

I initially thought I could just specify multiple entry points and ts-loader would use the correct tsconfig.json relative to each entry point. Any files it discovers in the dependency graph of the entry files would use the same configuration as the respective entry file.

Unfortunately this won't work—ts-loader will re-use the first TypeScript instance (and therefore config) it creates for both entry points.

I then discovered ts-loader's instance option, which forces ts-loader to create different instances. However, the "shared" code is difficult to place: it can be compiled with either TS configs!

In any case, below is what I have ended up with. Have you got any ideas how I could clean this up? Or how this might be made easier in the future?

Ideally, this would be as simple as tsc makes it. Feed it a configuration file, and it will discover the entry file(s) through files and compile all files in the dependency graph with the same configuration.

import * as path from 'path';
import * as webpack from 'webpack';

const SOURCE_PATH = path.join(__dirname, '..');
const SHARED_SOURCE_PATH = path.join(SOURCE_PATH, './src/shared');
const BROWSER_SOURCE_PATH = path.join(SOURCE_PATH, './src/browser');
const SERVICE_WORKER_SOURCE_PATH = path.join(SOURCE_PATH, './src/service-worker');

const config: webpack.Configuration = {
    devtool: 'source-map',
    entry: {
        browser: path.join(BROWSER_SOURCE_PATH, './main.ts'),
        'service-worker': path.join(SERVICE_WORKER_SOURCE_PATH, './index.ts'),
    },
    output: {
        path: path.join(SOURCE_PATH, './target'),
        filename: '[name].js',
    },
    resolve: {
        extensions: [
            // start defaults
            '.js',
            '.json',
            // end defaults
            '.ts',
        ],
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                include: [SHARED_SOURCE_PATH],
                loader: 'ts-loader',
                options: {
                    instance: 'shared',
                },
            },
            {
                test: /\.ts$/,
                include: [BROWSER_SOURCE_PATH],
                loader: 'ts-loader',
                options: {
                    instance: 'browser',
                },
            },
            {
                test: /\.ts$/,
                include: [SERVICE_WORKER_SOURCE_PATH],
                loader: 'ts-loader',
                options: {
                    instance: 'service-worker',
                },
            },
        ],
    },
};

export default config;

Activity

  1. johnnyreilly commented on Sep 29, 2017

    @johnnyreilly
    Member

    That's not the worst of configs even if it's a bit verbose. Completely on a side note but I'd been thinking about how ts-loader might well be used with service workers (I love a PWA) and this is a nice approach.

    I initially thought I could just specify multiple entry points and ts-loader would use the correct tsconfig.json relative to each entry point.

    See #267 - this is not supported at present although if @tkrotoff has a go then it could happen and that might improve your config.

  2. johnnyreilly commented on Sep 29, 2017

    @johnnyreilly
    Member

    Do you want to try using the configFile option as suggested by @dbettini in #54:

    {
      test: /\.tsx?$/,
      include: "./path/to/entry/index.ts",
      use: [
        {
          loader: "awesome-typescript-loader",
          options: {
            instance: "myInstanceName",
            configFileName: "./path/to/entry/tsconfig.json"
          }
        },
        "angular-router-loader",
        "angular2-template-loader"
      ]
    }
    

    I believe ts-loader should support this too

  3. OliverJAsh commented on Sep 29, 2017

    @OliverJAsh
    Author

    The "shared" code doesn't have a TS project of its own, but it is implicitly part of the dependency graph of both the "browser" and "service worker" TS projects. However, using the configuration above, webpack/ts-loader will attempt to find a unique TS config file for "shared", leading to errors:

    ❯ webpack --config ./target/webpack.config.js
    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/browser/tsconfig.json
    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/service-worker/tsconfig.json
    ts-loader: Using typescript@2.5.2
    Hash: 9f69766a7184ab47e3c3
    Version: webpack 3.6.0
    Time: 2317ms
                    Asset     Size  Chunks             Chunk Names
        service-worker.js  68.5 kB       0  [emitted]  service-worker
               browser.js   5.6 kB       1  [emitted]  browser
    service-worker.js.map  87.4 kB       0  [emitted]  service-worker
           browser.js.map  8.34 kB       1  [emitted]  browser
       [4] ./src/browser/index.ts 3.06 kB {1} [built]
       [5] ./src/service-worker/index.ts 1.62 kB {0} [built]
      [17] ./src/shared/types.ts 94 bytes {0} [built] [failed] [1 error]
        + 15 hidden modules
    
    ERROR in ./src/shared/types.ts
    Module build failed: error while parsing tsconfig.json
     @ ./src/service-worker/index.ts 5:16-42
    

    If I remove the rule for "shared", it will fail to compile the TypeScript altogether:

    ❯ webpack --config ./target/webpack.config.js
    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/browser/tsconfig.json
    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/service-worker/tsconfig.json
    Hash: 3f14b3c24b6945bfe3ca
    Version: webpack 3.6.0
    Time: 2446ms
                    Asset     Size  Chunks             Chunk Names
        service-worker.js  68.7 kB       0  [emitted]  service-worker
               browser.js   5.6 kB       1  [emitted]  browser
    service-worker.js.map  87.4 kB       0  [emitted]  service-worker
           browser.js.map  8.34 kB       1  [emitted]  browser
       [4] ./src/browser/index.ts 3.06 kB {1} [built]
       [5] ./src/service-worker/index.ts 1.62 kB {0} [built]
      [17] ./src/shared/types.ts 297 bytes {0} [built] [failed] [1 error]
        + 15 hidden modules
    
    ERROR in ./src/shared/types.ts
    Module parse failed: /Users/OliverJAsh/Development/twitter-paper/src/shared/types.ts Unexpected token (3:7)
    You may need an appropriate loader to handle this file type.
    | import * as t from 'io-ts';
    |
    | export enum PushEventTypeT {
    |     publication = 'publication',
    | }
     @ ./src/service-worker/index.ts 5:16-42
    

    Ideally, when webpack traces an entry file, it will re-use the same TS config for the dependency graph of that entry file. This is what happens when I run tsc and specify a config file. In this case that would mean "shared" is type checked against the config of both projects.

    I can workaround this by creating a TS config file for "shared", however this means my IDE/tsc configuration will be different from my webpack configuration. 🤔

  4. dbettini commented on Sep 29, 2017

    @dbettini

    Try setting your rules like this:

    rules: [
                {
                    test: /\.ts$/,
                    include: [BROWSER_SOURCE_PATH, SHARED_SOURCE_PATH],
                    loader: 'ts-loader',
                    options: {
                        instance: 'browser',
                        configFileName: "./path/to/browser/tsconfig.json"
                    },
                },
                {
                    test: /\.ts$/,
                    include: [SERVICE_WORKER_SOURCE_PATH, SHARED_SOURCE_PATH],
                    loader: 'ts-loader',
                    options: {
                        instance: 'service-worker',
                        configFileName: "./path/to/service-worker/tsconfig.json"
                    },
                },
            ],
    
  5. OliverJAsh commented on Sep 29, 2017

    @OliverJAsh
    Author

    @dbettini That does the trick! Using configFile instead of configFileName as the latter is deprecated. Thank you!

    One improvement could be to remove the need for instance if the configFiles are different 🤔

  6. dbettini commented on Sep 29, 2017

    @dbettini

    @OliverJAsh just to be sure, did you only add the configFileName, or did you also need to add SHARED_SOURCE_PATH to include?

  7. OliverJAsh commented on Sep 29, 2017

    @OliverJAsh
    Author

    @dbettini I added configFile and SHARED_SOURCE_PATH in include:

    import * as path from 'path';
    import * as webpack from 'webpack';
    
    const ROOT_PATH = path.join(__dirname, '..');
    const TARGET_PATH = path.join(ROOT_PATH, './target-webpack');
    
    const SHARED_SOURCE_PATH = path.join(ROOT_PATH, './src/shared');
    const BROWSER_SOURCE_PATH = path.join(ROOT_PATH, './src/browser');
    const SERVICE_WORKER_SOURCE_PATH = path.join(ROOT_PATH, './src/service-worker');
    
    const config: webpack.Configuration = {
        devtool: 'source-map',
        entry: {
            browser: path.join(BROWSER_SOURCE_PATH, './index.ts'),
            'service-worker': path.join(SERVICE_WORKER_SOURCE_PATH, './index.ts'),
        },
        output: {
            path: TARGET_PATH,
            filename: '[name].js',
        },
        resolve: {
            extensions: [
                // start defaults
                '.js',
                '.json',
                // end defaults
                '.ts',
            ],
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    include: [BROWSER_SOURCE_PATH, SHARED_SOURCE_PATH],
                    loader: 'ts-loader',
                    options: {
                        instance: 'browser',
                        configFile: path.join(BROWSER_SOURCE_PATH, 'tsconfig.json'),
                    },
                },
                {
                    test: /\.ts$/,
                    include: [SERVICE_WORKER_SOURCE_PATH, SHARED_SOURCE_PATH],
                    loader: 'ts-loader',
                    options: {
                        instance: 'service-worker',
                        configFile: path.join(SERVICE_WORKER_SOURCE_PATH, 'tsconfig.json'),
                    },
                },
            ],
        },
    };
    
    export default config;
  8. dbettini commented on Sep 29, 2017

    @dbettini

    @OliverJAsh yep, as expected. The reason it's needed is because webpack never had .ts rules set for your shared folder. And using a third shared instance was not an option, because your shared folder is not an entry point, so webpack wouldn't build it even if you did have a tsconfig.json in it. What was needed was giving webpack access to your shared folder in both of your instances that build your entries, because it is their dependency, but since it's in the same directory level, you need to add it to include.

  9. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author

    @dbettini Just spotted a problem: for some reason, webpack says there is an error but provides no details why (using the config above):

    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/browser/tsconfig.json
    ts-loader: Using typescript@2.5.2 and /Users/OliverJAsh/Development/twitter-paper/src/service-worker/tsconfig.json
    Hash: 005abdd80603e0885980
    Version: webpack 3.6.0
    Time: 3159ms
                    Asset     Size  Chunks             Chunk Names
        service-worker.js  69.5 kB       0  [emitted]  service-worker
               browser.js   5.6 kB       1  [emitted]  browser
    service-worker.js.map  89.8 kB       0  [emitted]  service-worker
           browser.js.map  8.34 kB       1  [emitted]  browser
       [5] ./src/browser/index.ts 3.06 kB {1} [built] [1 error]
       [6] ./src/service-worker/index.ts 1.62 kB {0} [built]
      [17] ./src/shared/types.ts 1.07 kB {0} [built]
        + 15 hidden modules
    

    Note the "[1 error]" beside /src/browser/index.ts.

    If I enable only one entry point and the corresponding rule entry, neither "browser" or "service-worker" will error. But when I run both using the above config, webpack reports an error for the file "/src/browser/index.ts" but with no further details.

    @johnnyreilly Do you have any ideas here? Is there a way to log more info about the error?

  10. dbettini commented on Sep 30, 2017

    @dbettini

    @OliverJAsh like I said, I use at-loader, I always get all errors logged by default when using it. If ts-loader doesn't log errors by default, maybe you can try changing logLevel

  11. johnnyreilly commented on Sep 30, 2017

    @johnnyreilly
    Member

    It should log all errors - I'd advise forking ts-loader and adding in some extra logging to diagnose. Do you have a minimal repro repo you could share?

  12. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author
  13. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author

    For some reason, specifying "module": "es2015" compiler option in both tsconfig.jsons will fix the error. I don't really understand why though 🤔 https://github.com/OliverJAsh/webpack-ts-loader-shared/tree/es-module-compiler-option

    In any case, it would be good to get the error message printed.

  14. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author

    One problem with the suggested config above is the "shared" code will only be type checked once, using whichever rule is found first. Both "browser" and "service-worker" include "shared", but when webpack compiles "shared", it won't use the tsconfig.json corresponding to that entry file—it will just use the tsconfig.json instance of the first matching rule. @dbettini

  15. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author

    Edit: upon further testing, this is wrong. Ignore this comment.


    I just gave this a go with awesome-typescript-loader. A few things to note:

    • "shared" will be type checked against both tsconfig.jsons, not just the first matching
    • In my webpack config I don't have to include "shared" in either rules. Somehow at-loader knows to use the same tsconfig.json for all files in the dependency graph of the entry file

    Example: https://github.com/OliverJAsh/webpack-ts-loader-shared/tree/awesome-typescript-loader

    Here is an example using at-loader where "shared" is type checked for both tsconfig.jsons, and will fail for one of them: https://github.com/OliverJAsh/webpack-ts-loader-shared/tree/awesome-typescript-loader-shared-type-check

  16. johnnyreilly commented on Sep 30, 2017

    @johnnyreilly
    Member

    Sorry, does it work with at or not?

    The module thing might be because (at present) ts-loader has different module defaults to tsc. We plan to rectify this with V3 (being worked on now - see #637)

  17. OliverJAsh commented on Sep 30, 2017

    @OliverJAsh
    Author

    At this time I can't say it does work any better with at-loader—sorry to cause confusion!

  18. johnnyreilly commented on Oct 13, 2017

    @johnnyreilly
    Member

    No worries 😄

  19. OliverJAsh commented on Oct 17, 2017

    @OliverJAsh
    Author

    Closing as a solution was found (with trade offs). Thanks again.

  20. johnnyreilly commented on Oct 17, 2017

    @johnnyreilly
    Member

    Could you share the solution for others please? Always helps!

  21. OliverJAsh commented on Feb 7, 2020

    @OliverJAsh
    Author

    ~3 years later, sorry! My solution was just to use one tsconfig.json.

    (We still want to keep separate projects, but project references still have issues with VS Code.)

  22. johnnyreilly commented on Feb 7, 2020

    @johnnyreilly
    Member

    Haha! Props for replying after 3 years 😄

  23. yemi commented on Jul 23, 2020

    @yemi

    Thought I'd keep this one alive just a bit longer; I'm running ts-loader alongside serverless framework, specifically the serverless-webpack plugin, which works fine, and FWIW has a helper for declaring multiple entrypoints (one for each lambda). These compile fine when used at the root project (~1s) incremental compilation, but when ANY sub project gets changed, it emits changes for each of the entrypoints, causing a whole sea of recompilations to occur.

    Any thoughts on what I can do to prevent that from happening?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions