Include importable modules from outside project folder for webpack HMR Vue.js
Asked Answered
O

5

11

I have a project structure like:

.
+-- Common
|   +-- MyCommonVueComponent.Vue
+-- MainProject
|   +-- webpack.config.js
|   +-- package.json
|   +-- node_modules
|   +-- src

When I a build from the console webpack does not complain as it seemingly has a correct path to the node_modules folder for components imported from Common into MainProject. When I attempt to debug the Vue.js app in the browser I get the following error:

../Common/MyCommonVueComponent.Vue
Module not found: Error: Can't resolve 'vue-hot-reload-api' in 'D:\Projects\Cb\CommonVue'

I've added:

resolveLoader: {
        modules: [path.resolve(__dirname, './node_modules')],
    },

And that did seem to resolve path issues when running webpack in the console but not when debugging in the browser. Any help is appreciated. Hopefully someone who has setup a similar project structure can shed some light!

Ouellette answered 1/8, 2018 at 22:29 Comment(0)
H
5
  • Vue-cli: v3.11.0
  • webpack: v4.40.2
  • babel: 7.6.0
  • node: v10.16.3

I just ran into a similar situation tonight where I wanted to move out a few custom libraries into an external shared lib directory for agile development and mocked testing. I just started learning the Vue/webpack ecosystem about two weeks ago, and I can definitely say it's been an adventure with webpack being a daunting beast in figuring out all the mystical knobs and buttons that have to be aligned just right.

./devel  (webpack alias: TTlib)
+-- lib
|   +-- widget.js
|   +-- frobinator.js
|   +-- suckolux3000.js
+-- share (suckolux3000.js taps into this directory)
|   +-- imgs
|   +-- icons
|   |   +-- img
|   |   +-- svg
+-- MainProject
|   +-- vue.config.js
|   +-- package.json
|   +-- node_modules
|   +-- src
+-- TestProject
|   +-- vue.config.js
|   +-- package.json
|   +-- node_modules
|   +-- src

My concern was making sure that webpack HMR would work, and I can say you were close, but did not go far enough. Fortunately the fix was pretty simple.

Here are my vue.config.js additions:

module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        'TTlib': '/devel/lib'
      },
      modules: ['/devel/lib']
    },
    resolveLoader: {
      modules: ['/devel/lib']
    }
  }
};

Now from within any of my *Project components, I can just use:

import frobinator from 'TTlib/frobinator'

Now if I edit any of the project or (external) lib files, webpack HMR will kick in and refresh the running 'yarn serve' daemon and emit the changes I've made.

Hope that helps!

Hypercritical answered 26/9, 2019 at 12:49 Comment(0)
H
5

I had been using Terra's solution for a while for a similar situation and this works fine, but there was one downside for me, namely if the shared code also requires node_modules content of their own, then intellisense didn't work and when building the project, errors would appear (Cannot find module ...) even though everything does work fine in the browser. Maybe it's because I'm using TypeScript? Either way, using separate packages or advanced tools like bit seemed like too much of a hassle, so I went to look for a simpler solution without the downside.

I've put my attempts in this repository: https://github.com/brease-colin/vue-typescript-shared where there's one project folder (project1) and three shared folder attempts, all linked in project1 at the same time, all with their own downside(s), but I'm planning on using attempt 3 as it's not really a downside for us.

BTW, there's one issue that was common for all three, but maybe that was my VSCode acting weird. If opening the root folder in VSCode, then Intellisense within Vue files didn't understand any of the three solutions' imports, while if opening the project1 folder in VSCode, it understood imports for all three. The below description of each attempt assumes opening project1 in VSCode.

Main files to look at are:

  • project1/src/components/HelloWorld.vue : vue file that attempts to reuse a shared composition function and a shared component
  • project1/src/data/ProjectDummies.ts : ts file that attempts to reuse a shared ts file
  • project1/tsconfig.json
  • project1/vue.config.js
  • shared[1/2/3]/components/Header[1/2/3].vue : a shared component that imports from @vue/composition-api.

Attempt 1: shared1 with alias @s1

This is a Typescript version of Terra's answer. I've only added a path and two include paths to the tsconfig file to make intellisense work. Also check out shared1/tsconfig.json, because there I've added the same alias (@s1), so references between shared files go well.

Pros: no additional setup needed, all works well in the browser, no warnings / errors in console, you can easily open shared1 as additional folder in your VSCode workspace, so you can edit all shared1 files.

Cons: references to node_modules do not work in VSCode and an error is output in the terminal for that as well. As such, no intellisense / no type safety when using the classes in your project folder. That last part is a big con for me.

Attempt 2: shared2 with alias @s2

To try and fix it, I've added a package.json to the shared folder and installed the required packages (in this case: @vue/composition-api) for the shared files. This made the intellisense work and the terminal output error upon build is also gone. However, now the code breaks down on runtime in the browser, because the dependencies are added twice and imports in the shared folder do not refer to the same module code. This won't give errors in all types of dependencies, but in some cases, some constants are initialized that should be the same across the whole codebase. The errors I got were:

[Vue warn]: onMounted is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup().

[Vue warn]: Error in data(): "Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function."

found in

---> <Header2> at shared2/components/Header2.vue
   <HelloWorld> at src/components/HelloWorld.vue
     <Home> at src/views/Home.vue
       <App> at src/App.vue
         <Root>

I've tried fixing this by changing the module resolve / resolveLoader, making sure that it first checks the main dir's node_modules before it tries looking the 'regular' way, but it didn't seem to help.

modules: [
  path.resolve(__dirname, 'node_modules'),
  'node_modules',
],

Maybe someone has a way to fix it properly?

Pros: Intellisense works, no more build errors.

Cons: Runtime errors, no working application, plus extra complexity / risks by having to keep the modules at the same version numbers between project1 / shared2 to keep intellisense working consistently.

Attempt 3: shared3 with alias @s3

Code wise, attempt 3 is not so different from attempt 1, but using a simple trick, everything works as I wanted it. The trick is using a symlink and I've used a multiplatform npm package called symlink-dir to do that easily. I've actually added it as a dev dependency to the project1's package.json:

npm install --save-dev symlink-dir

Afterwards, I've made the symlink as below, which you have to do once every time you clone the repository.

npx symlink-dir ../shared3/ shared3/

Now, to prevent from checking in the code twice, you'll have to add a line to your .gitignore:

// .gitignore
*/shared3/

Because the shared code is now within your project folder, you could even access it without an alias, but to more easily let files in the shared folder access eachother, I do prefer a fixed alias, so I added it by adding the following configuration to vue.config.js and tsconfig.json:

// vue.config.js
configureWebpack: {
  resolve: {
    alias: {
      '@s3': path.resolve(__dirname, 'shared3'),
    },
  }
},

// tsconfig.json
"compilerOptions": {
  // only needed for auto completion(?)
  "paths": {
    "@s3/*": ["shared3/*"],
  },
},

Although I would prefer a config only solution to improve shared1, I'm still very happy with this end result as it works perfectly for us as a small team.

Pros: No warnings, errors, etc.: all works as if the code is in the project, which kind of is the case, while still having the code accessible in other projects.

Cons: Every developer needs to create the symlink manually before their code will compile / work with intellisense. Then again, they also have to run npm install for that.

Harrar answered 1/8, 2020 at 20:9 Comment(0)
O
3

**** Update ****

So this was a bit of hell. To me this setup is the ideal setup for a small team (1-4). You don't want to deal with npm packaging or creating an additional repo if you already have a parent repo (monolithic) with child projects in it. You want to be able to develop and debug the components right in your projects. Much faster than updating, packaging, sucking into another package. I finally got everything working except the webpack HMR on projects that were consuming the components from the Common folder. here's what ended up working for me:

adding to webpack.config.js above resolve:

resolveLoader: {
    modules: [path.resolve(__dirname, 'node_modules')],
},

adding an alias in resolve (webpack.config.js) for the Common folder:

'Common': path.resolve(__dirname, '../Common') <- this is the root of my mono repo

modifying my output in webpack.config.js (added publicPath):

output: {
        path: path.join(__dirname, bundleOutputDir),
        filename: '[name].js',
        publicPath: 'dist/'
    },

This seems to work so far. This allows me to stick straight .Vue component files in a folder called Common that is a sibling to all my other project files. All projects including common site in a main solution folder which is the root of my git repo.

Alternatives are using NPM or using bit (https://bitsrc.io) . All of these solutions seem more clunky and less frictionless than the above.

Ouellette answered 2/8, 2018 at 19:59 Comment(1)
this is really nice!!!!Houseclean
W
0

Delete the package folder inside node_modules and created a symbolic link to the real project inside node_modules.

rm -rf my_project/node_modules/shared_lib

ln -s /path/shared_lib my_project/node_modules/shared_lib
Withers answered 14/10, 2020 at 20:24 Comment(0)
D
0

Coming from ASP.NET development where we use a similar setup this was the project structure we where looking for in our JavaScript development environment. We had all the same issues trying numerous baseurl/paths/alias combinations and all of them resulting in some sort of failed es-linting, typescript processing, module loading or other.

The solution which worked for us in the end was actually quite simple.

We used symbolic links to map in the common project folders. To add a symbolic link run CMD as administrator and execute mklink /D "C:\SymbolicLink\To" "C:\Actual\Common\Directory"

Once this is done you need to change one critical setting in the vue.config.js file. Without this setting dependencies will be resolved from their real location instead of the symbolic link location.

configureWebpack: {
    resolve: {
      symlinks: false // Dependencies must be resolvable from the symlinked location, not the real location
    }
}

That should be all you need.

Daves answered 14/9, 2021 at 8:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.