webpack-plugin
webpack-plugin copied to clipboard
Unexpected behavior with monorepo
I'm submitting a bug report
- Library Version: 3.0.0-rc.1
Please tell us about your environment:
-
Operating System: Windows 10
-
Node Version: 6.11.2 | 8.10.0
- NPM Version: 3.10.10 | 5.6.0
- JSPM OR Webpack AND Version webpack 4.5.0
-
Browser: Chrome 65.0.3325.181 | Firefox 59.0.2 | Edge 41.16299.371.0
-
Language: TypeScript 2.8.1
Current behavior: The plugin is not working as expected when packages are symlinked. To demonstrate the behavior, I have created a repository. I have a monorepo (using lerna) with following structure:
|-modules
| |-main // aurelia-webpack project
| |-myplugin // aurelia plugin as npm module
| |-services // an npm module containing services
|-package.json // contains npm scripts using lerna to build app
If I want to register some module specific routes from myplugin
, I need to use
{ moduleId: PLATFORM.moduleName("./views/plugin-home"), route: "myplugin" }
With this, the webpack build for main
works iff I set resolve.symlinks: false
in my webpack config. However, this causes another problem which portrays as if the DI is failing. To clarify, let's assume that there is a MyService
exported from services
package, which is used in both main/main.ts
and myplugin/index.ts
with Container.instance.get
. Now, one would expect that same instance will be provided in both the places. However, that is not the case as MyService
is being resolved from 2 different sources:
- For
main/main.ts
: frommain/node_modules/services/...
- For
myplugin/index.ts
: frommain/node_modules/myplugin/node_modules/services/...
(as compiled frommain
) And this problem can only be resolved usingresolve.symlinks: true
, which provides unique path for both cases. One can verify using the provided repository and tweaking the configurations.
Expected/desired behavior:
-
What is the expected behavior? The expected behavior is that one configuration should support both the cases. Also if possible, please add documentation/best practices for monorepo.
-
What is the motivation / use case for changing the behavior? In a monorepo application one would ideally like to symlink the local dependencies, so that a simple
watch
task to build the sources, makes the changes immediately available for the running application.
A workaround for this can be to define alias
in webpack config for the shared modules (services
in the above example). With that, the duplication of nested modules can be avoided, even with symlink
set to false
.
...
resolve: {
alias: {
'services': path.resolve(__dirname, "../services"), ...
} ...
}, ...
However, this can also be cumbersome when there are several shared modules.
I've had problems with monorepo recently as well.
It can get hairy rather quickly and there are many variations. For example you did not consider what happens if you hoist your node_modules
at root.
I am not sure what the best solution here is. Any idea is welcome.
Maybe I should add a mono-repo mode to the plugin?
If I try to sum up the options so far:
-
symlinks: false
kind of works but has duplicated modules if they are not hoisted. There was a webpack plugin to dedupe modules, would that help? If modules where hoisted would that be better (can we force it)? -
symlinks: true
will surely fail becauseAureliaWebpackPlugin
can't establish module names for things outside your project root. Is this the only problem? It might be fixable with clever config or maybe a slight change to the plugin. For example, defining aliases might help even though you don't use them. Try `"myplugin": "../myplugin/dist" or what is appropriate for you.
@jods4 I indeed hoist
ed the dependencies to root node_modules
folder, but still the local node_modules
(the ones, which are in my modules
folder), stays in individual node_modules
folder even if I have hoist
ed. And I have not found (or maybe I have missed) any lerna documentation to avoid that using npm
.
However, when one uses yarn (workspace) + lerna, all dependencies for all projects in a monorepo can be hoisted in the root node_modules
folder. And with that the alias can be removed. You may check it here in my repository's yarn-ws
branch.
And regarding the dedupe webpack plugin, I think it is long deprecated in favor of npm dedupe
(which does not work in my case) and yarn workspace (I think).
as far as I remember there are options in yarn workspaces to keep some of the modules.. not hoisted.. would that help?
@Alexander-Taran With yarn workspace, it works properly. In that setup as well, one need to use symlink: false
, but no alias is required.
I might be having a similar issue.
My structure is something like this:
|- clients
| |- common // an aurelia plugin containing common reusable components
| | |- comps_dir
| | |- index.js
| |- app1 // aurelia webpack app
| | |- src
| | | |- ... // local resources for this app
| | | |- main.ts // calls /clients/common as a plugin/feature
| | |- webpack.config.js
| | |- package.json
| |- app2
| | |- ...
| |- app3
| | |- ...
from within the webpack config of each app, I'm adding the common directory as a module and alias:
resolve: {
extensions: ['.js'],
modules: [path.resolve(__dirname, '../common'), 'src', 'node_modules'],
alias: {
common: path.resolve(__dirname, '../common')
}
},
Despite the common modules being resolved and loaded by the individual apps, the modules doesn't seem like they're getting transpiled by babel since I'm getting syntax errors:
ERROR in ../common/elements/xxx.js
Module build failed: SyntaxError: C:/Users/xxxx/Documents/Projects/xxx/clients/common/elements/xxx.js: Unexpected token (4:0)
2 | import { ApiService } from 'services/api';
3 |
> 4 | @inject(ApiService)
| ^
5 | export class UiXxx {
6 | constructor(api) {
7 | this.api = api;
My commons dir doesn't compile itself. It's literally just all the source files since I expect the apps using them to compile these along with everything else. These common modules can't go on their own builds, since Sass files inside common
are tightly coupled with the architecture of each app. Am I doing something wrong here?
Btw, all the solutions at https://github.com/babel/babel-loader/issues/293 doesn't seem like they work for my case.
@jemhuntr I think this is a different issue.
You have not specified if you are using lerna or not. If not you may benefit from using something like lerna or yarn workspace.
I would suggest to build your common module as an npm package. If there are some app specific stuffs inside your common module, then it is not really common. If you want to style your apps and put all css in a common stylesheet, then a better alternative would be to use css modules. Here are some resources for css module:
- Introduction: https://css-tricks.com/css-modules-part-1-need/
- How to use it with aurelia+webpack: part 1, part2
Then you can build your common module and use lerna to add it to your apps. That way the common module will reside in node_modules
of each app. And you don't have define the alias for common module unless there is a nested dependency, such as app-->common
and app-->someotherModule-->common
.
Hope this helps.
@Sayan751 Yeah, I'm not using lerna nor do I have a monorepo structure. This is just the most relevant resource I found about the specific issue, and I figured out eventually that the issue is most likely with webpack upstream.
And thanks, CSS Modules is definitely the way to go. The last time I tried getting it to work didn't exactly go well though, but I might try it again on my next project.
I really just have been looking for an easy way to share local resources between multiple webpack builds, as the apps are literally just copy-pastes of each other with different implementations and content structure.