Share code between two repos that require different versions of node - yarnpkg

I have a monorepo with this dependency tree:
/root
package.json
/packages
/api <--- requires node v16.x because of a critical dependency
package.json
/ui <--- requires node v14.x because Vercel does not yet support v16.x
package.json
/shared <--- shared code between both /ui and /api
package.json
I'm unable to deploy the /ui package on Vercel because it complains of about the dependency under /api that requires v16.
Is there a way to configure yarn workspaces such the offending dependency can be ignored when building /ui, or is my only option to eject from yarn workspaces and find some other way to share the /shared code?
Another way of asking the question:
I have repo A which must run on node 16, and repo B which must run on node 14. How can I share code between the two repos? Yarn workspaces isn't working for me because it forces all packages to conform to a single node version.

I ended up ejecting /ui from yarn workspaces, and continuing to share code by just importing the files from outside of the package's root directory.
I had a lot of file includes that utilized the following convention because of yarn workspaces:
import { someFunc } from '#project/shared/someFile'
Quite elegantly, I was able to continue sharing code without having to change that syntax at all - by simply updating jsconfig.json to simulate yarn workspaces:
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": ".",
"paths": {
"#project/shared/*": ["../shared/*"]
}
}
}

Related

Is there a way to use one Vendor for multiple projects with different seeder

i was having issues on how vendor take too much place. Since my host has a limit on number of files and folders (INODES). So i found a solution to use one Vendor for multiple project in laravel through the following link : Using one Vendor Folder for Multiple Projects in Laravel 5.2.
In the following lines I put the vendor in projectA, then link the others projects to it.
In projectA everything works well when i do php artisan migrate:fresh --seed
When i try the same thing to projectB for example, the above command works till it arrives where the seeders should be executed. There, some error occur due to the fact that the command is trying to launch a seeder of projectA in the projectB as shown in the following screenshot.
Seeder Error Screenshot
So i want to know if there's a way to make the seeder separately.
I tried to create a symlink on the composer.json file as it was done for the vendor folder, but it doesn't work.
I'm using Laravel 8
I thínk this is a terrible idea, except in the case that both projects share exact same codebase.
Composer use composer.json as you know. How does your B project composer file determines how to resolve PSR4 autoload entries in Laravel, If it is symlinked to A project? That is probably the reason why your seed command is trying to locate Classes in A project.
Composer is clever enough to cache downloaded packages and reuse them, but I think that every project has its own dependencies and state, which is maintained by composer.json in the first case and composer.lock on the state case.
What happen if you update composer in A but not in B, will B work?
And last, composer autoload file reference all satisfied dependencies in your project, and in this case that (unique) autoload file will be loaded in both projects but what happen if your required packages are not exactly the same? ie You have Laravel Debug Bar in one project but not in the other. The autoload generated file will reference that package that will not exist on your other project.
Is not this the way composer work? Am I wrong?
In Composer the Vendor Directory (vendor in the project tree by default) is per project.
You ask about how to use one vendor folder for different projects.
Now first of all, this is absolutely not what Composer expects nor how it works. See Manuel Glez answer. In short a terrible idea.
When it comes to Composer, the place to share the actual PHP code across projects is not in the vendor directory but in repositories.
And as long as the dependencies are compatible, you could make one project depend on another and use its vendor/<vendor>/<name> folders as repositories. The remarks in Manuel Glez answer are still the same, this need to be same compatible versions across the board.
But to give the example, see Composer Path Repository which has this layout:
...
├── apps
│ └── my-app
│ └── composer.json
├── packages
│ └── my-package
│ └── composer.json
...
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package"
}
]
}
It can be adopted for each ../../project/A/vendor/<pkg-vendor>/<pkg-name> in ../../project/B/composer.json so that the vendor folder in project/A can act as a path repository for project/B.
As dependencies composer.json files normally do not contain the version, the documented remarks about repositories.options.versions apply:
When the version cannot be inferred from the local VCS repository, or when you want to override the version, you can use the versions option when declaring the repository:
{
"repositories": [
{
"type": "path",
"url": "../../packages/my-package",
"options": {
"versions": {
"my/package": "4.2-dev"
}
}
}
]
}
To prevent the duplication of the files the default strategy for Composer is to symlink the package directories. Ensure it works, then you only have one symbolic link per dependency in project B.
Okay how cool is that? Well IMHO while you still give up much of what Composer can do for you for dependency management, this at least makes use of local Composer repositories which I'd recommend for sharing instead of completely symlinking the overall vendor folder. Each project still have its own vendor/composer setup and overall what is done is much more well defined and in line with Composer itself.
Whether this works or not depends on the individual case. Key point here is as these local repositories only provide a single version per each package, you can only have that one. So these versions must all be version compatible on API level.
The system where it runs needs to support (relative) symbolic links, this should be commonly available for the situation described.
You could then automate the production of the repositories configuration and adopt it to the file-system layout. You could even generate the repositories and update them in the global configuration file so that each project would automatically prefer those packages from local.
$ echo "$(composer config --global home)/config.json"
/home/user/.config/composer/config.json
(compare: COMPOSER_HOME/config.json (Composer docs))
Take care all projects and their dependencies have a portable path-profile and then I'd say this should be quite straight forward shell processing.
To obtain the actual versions of the dependencies installed check per each vendor folder inside vendor/composer/*installed* files.
$ (echo "PACKAGE VERSION"; find .. -type f -path '*/vendor/composer/installed.json' -exec jq -r '.packages[] | .name + " " + .version_normalized ' {} \; | sort -u | sort -k 1b,2V) | cols
PACKAGE VERSION
composer/ca-bundle 1.3.2.0
composer/composer 2.3.7.0
composer/metadata-minifier 1.0.0.0
composer/pcre 3.0.0.0
composer/semver 3.3.2.0
composer/spdx-licenses 1.5.7.0
composer/xdebug-handler 3.0.3.0
...
phar-io/manifest 1.0.1.0
phar-io/manifest 1.0.3.0
phar-io/manifest 2.0.1.0
phar-io/manifest 2.0.3.0
...
(very old installations don't have the packages keyword, you'll likely want to filter)
Finally you may want to have something to smoke-test the setup easily so that you can have guards against the dependency incompatibility problems when you take notice of them.

npm publish (using also lerna) is ignoring ts definition file

Hi all I am creating this public library
https://github.com/kristijorgji/winstonjs-utils using ts+lerna monorepo
My issue is that although having specified in every package.json
"files": [
"./dist"
],
The file dist/index.d.ts is ignored by the publish command
When I build typescript , the dist folder has two files
But in the published package under dist exists only index.js
I do not have any .npmignore file as you can also see in the public repo I shared.
In all packages packages.json I have specified the typings as well
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"./dist"
],
Any idea why index.d.ts is excluded from the published packages ? Thanks
I managed to find one solution although might not be the best one.
Nevertheless I am posting in lack of an answer to help everyone facing same issue as me
Solution: I used .npmignore instead of files in package.json
files key seem broken because it ignores index.d.ts files
I used a .npmignore file that excludes unecessary things like
__tests__
coverage
src
tasks
.eslintrc.js
jest.config.js
nodemon.debug.json
nodemon.json
tsconfig.json
then publishing the package worked great with same result as I intended, having only dist folder with index.js index.d.ts inside and package.json and readme.md

Yarn installing wrong version of jest in workspace

I have a project with yarn (v1) workspaces.
project/
packages/
pkgA/
subPkgA1/
pkgB/
package.json has this:
"workspaces": {
"packages": [
"packages/pkgA",
"packages/pkgA/subPkgA1",
"packages/pkgB"]}
note that there's a nested workspace in there.
Top-level has jest#27.4.7 in its package.json.
pkgA has jest#24.9.0 because one of its dependencies requires that version; its package.json has no entry for jest.
pkgA/subPkgA1 has jest#27.4.7 in its package.json, BUT it actually gets 24.9.0 installed in its node_modules/.bin/jest, presumably because its parent workspace has that.
My main question is how to get jest#27.4.7 in the nested package subPkgA1? I tried adding an entry in pkgA's package.json, but that didn't help.
BTW, I know nested workspaces aren't fully supported until Yarn v2, but I can't upgrade right now, and I can't easily move my subpackage out of its parent, so any hacky solution would be better than nothing.

How can I package symbolic link in node_modules with `serverless?

I am using serverless to package nodejs application. I am using yarn workspace in my project.
- common
- projectA
- projectB
the projectA and projectB are using common module which is managed by yarn workspace. It creates a link inside node_modules/common -> ../common. But when I package the application with sls deploy, it doesn't inlude the link node_modules/common. How can I make it package symbolic link?
You should start using code bundler.
What is code bundler?
What code bundler does:
It scans your AWS Lambda code structure through all of the files, starting from handler file.
It goes through all of the imports to create a tree of dependencies.
Then it inlines all of those dependencies into single "fat" file.
After that you are free to deploy your application, which has only single file.
As you can see, it's a perfect match for AWS Lambda and your use case.
All of the dependencies from common package will be included in the output file.
Also code bundlers have other cool features, like removing all of the unneeded files, that are defined in libraries that you use, but you are not using them directly. Due to this output package size of your Lambda will be a lot smaller, which will decrease cold starts.
How to achieve that using Serveless Framework
The easiest way is to start with serverless-webpack plugin, which includes Webpack (one of the most popular code bundlers) and some most common configurations for it.
After adding this plugin, simply configure it in serverless.yml:
custom:
webpack:
webpackConfig: 'webpack.config.js' # you can remove it, it's the same as default
packager: 'yarn'
Now you need to configure Webpack using webpack.config.js file. There are a lot of possibilities to configure it and the example below is the most basic one:
const path = require('path');
const slsw = require('serverless-webpack');
module.exports = {
entry: slsw.lib.entries,
target: 'node',
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
stats: 'minimal',
devtool: 'nosources-source-map',
externals: [{'aws-sdk': 'commonjs aws-sdk'}],
resolve: {
extensions: ['.js', '.json'],
},
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '.webpack'),
filename: '[name].js',
sourceMapFilename: '[file].map',
},
};
Now when you call sls package in projectA or projectB, then after unzipping ./.serverless/functionName.zip, you will find just single "fat" file, that will include all of the required dependencies.
During sls deploy phase, this file will be deployed as Lambda handler.
Correctly defining dependencies
Make sure, that common package is listed as dependency of projectA and projectB:
// common/package.json
{
"name": "#your-project/common",
"version": "1.0.0",
"license": "ISC",
}
// projectA/package.json
{
"name": "#your-project/packageA",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"#your-project/common": "1.0.0"
}
}
Thanks to this, you will be able to reference commons in pakcageA imports via:
import exampleHelper from '#your-project/common/src/exampleHelper';
Project using this approach can be found on my Github here:
https://github.com/PatrykMilewski/serverless-series

Yarn install a single package to single workspace

This is my project set up
proj:
package.json - workspaces["app/frontend", "app/backend"]
app
frontend - package.json
backend - package.json
say I cd to proj
I want to do yarn workspace app/frontend add uuid -dev (add a pkg to one of the workspace)
err is Unknown workspace "app/frontend", wonder what is the correct syntax?
yarn workspace frontend add uuid --save-dev
When you define your workspaces in the package.json you should use relative path to the workspace:
"workspaces": [
"app/frontent",
"app/backend"
]
However, when you refer to your workspace in yarn workspace ... command you should use the package name of this workspace (including namespace).
For example, if your frontend/package.json defines
{
name: "#myproj/frontend".
...
}
you will use
yarn workspace #myproj/frontent add uuid --save-dev

Resources