Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
OliverJAsh opened this issue Sep 28, 2017 · 23 comments
Closed

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

OliverJAsh opened this issue Sep 28, 2017 · 23 comments

Comments

@OliverJAsh
Copy link

OliverJAsh commented Sep 28, 2017

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;
@johnnyreilly
Copy link
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.

@johnnyreilly
Copy link
Member

johnnyreilly commented Sep 29, 2017

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

@OliverJAsh
Copy link
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. 🤔

@dbettini
Copy link

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"
                },
            },
        ],

@OliverJAsh
Copy link
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 🤔

@dbettini
Copy link

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

@OliverJAsh
Copy link
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;

@dbettini
Copy link

dbettini commented Sep 29, 2017

@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.

@OliverJAsh
Copy link
Author

OliverJAsh commented Sep 30, 2017

@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?

@dbettini
Copy link

@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

@johnnyreilly
Copy link
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?

@OliverJAsh
Copy link
Author

@OliverJAsh
Copy link
Author

OliverJAsh commented Sep 30, 2017

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.

@OliverJAsh
Copy link
Author

OliverJAsh commented Sep 30, 2017

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

@OliverJAsh
Copy link
Author

OliverJAsh commented Sep 30, 2017

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

@johnnyreilly
Copy link
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)

@OliverJAsh
Copy link
Author

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

@johnnyreilly
Copy link
Member

No worries 😄

@OliverJAsh
Copy link
Author

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

@johnnyreilly
Copy link
Member

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

@OliverJAsh
Copy link
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.)

@johnnyreilly
Copy link
Member

Haha! Props for replying after 3 years 😄

@yemi
Copy link

yemi commented Jul 23, 2020

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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants