How to handle the production build of a typescript monorepo (NestJS) with common librairies? [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 months ago.
Improve this question
I'm struggling with the production build of a typescript monorepo. The company I'm working for has a mono repository with multiple APIs (nestJS) and few common libraries (mainly typescript source code built using tsc) which I can't sadly share here.
The APIs are runnable using nest start and we can also access to the #nestjs/swagger deployed within the APIs without any problem. Until few weeks ago we were building the production artefact using nest build --webpack with the following webpack configuration :
const path = require("path");
const webpack = require("webpack");
module.exports = function (options) {
return {
...options,
mode: "production",
target: "node",
node: {
__dirname: false,
__filename: false
},
entry: "./src/main.ts",
output: {
path: path.resolve(__dirname, "build"),
filename: "main.js",
clean: true
},
externals: [...options.externals, "fastify-swagger"],
module: {
...options.modules,
rules: [
{
test: /\.(t|j)sx?$/,
exclude: /node_modules/,
use: {
loader: "ts-loader",
options: {
transpileOnly: true,
projectReferences: true
}
}
}
]
},
plugins: [
...options.plugins,
new webpack.IgnorePlugin({
checkResource(resource) {
const lazyImports = [
"#nestjs/microservices",
"#nestjs/microservices/microservices-module",
"#nestjs/websockets",
"#nestjs/websockets/socket-module",
"#nestjs/platform-express",
"#nestjs/swagger",
"#nestjs/mapped-types"
];
if (!lazyImports.includes(resource)) {
return false;
}
try {
require.resolve(resource);
} catch (err) {
return true;
}
return false;
}
})
]
};
};
We are not really sure of this configuration, it let use bundle our APIs as one file per API containing all the dependencies (which, based on my numerous researchs, is a bad pattern). Our usage of the IgnorePlugin is quite hazardous and conflict-driven.
Until now it was working fine but we are now unable to access the swagger page provided by #nestjs/swagger and configured within our APIs. The route exists but leads to a white page (the json containing the API description is loaded but every swagger static resource loading returns a 404).
I thought about a swagger static ressource copy but it seems to be a bad pattern. Moreover we do not have a node_modules since we are using Yarn with the Plug'n play nodeLinker.
In conclusion : after several days looking for good practices, examples I'm a bit lost since the problem presents different dimensions :
How to build each APIs and bundle it with its local dependencies (common libraries) ? The deployment is done using a docker image so we can install the external dependencies from the package.json at the image construction phase.
Do we really need webpack ?
How to serve the static resources of swagger ?
I followed the NestJS monorepo documentation but it doesn't correspond our problem since the company's monorepo contains severals APIs and their libraries but also a web application and a react-native application targeting Android. Both of those projects which are not nest projects are also using the shared libraries.
I'm ready to refactor the whole monorepo configuration but I need a good sample/example to base my work on.
Thank you for your help, pointers or any idea

Related

Module not found: Error: You attempted to import babel-preset which falls outside of the project src/ directory

I'm developing an application created using create-react-app
But then I needed to use mediainfojs library, this library requires wasm files, and based on what I understood I couldn't add it using create-react-app, I had to eject it.
After ejecting it, I went to mediainfo information on how to add the wasm on the webpack
They use the CopyPlugin, but then when I tried to do that it complained about the versions of my webpack (4) and the CopyPlugin.... so, I decided to migrate to webpack 5
That is when the pain starts... after follow their migration tutorial and do a bunch of modifications on my webpack.config I got to the following error while runing yarn build:
Module not found: Error: You attempted to import /MyWorkspace/project/node_modules/babel-preset-react-app/node_modules/#babel/runtime/helpers/esm/asyncToGenerator which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
The only place calling this babel-preset-react-app are in the configuation
Here:
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
options: {
customize: require.resolve(
"babel-preset-react-app/webpack-overrides"
),
And here:
{
test: /\.(js|mjs)$/,
exclude: /#babel(?:\/|\\{1,2})runtime/,
loader: require.resolve("babel-loader"),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve("babel-preset-react-app/dependencies"),
{ helpers: true },
],
],
cacheDirectory: true,
cacheCompression: isEnvProduction,
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
I have looked into similar issues reported here, but mostly of them seem to be related to either static files being dynamically imported or imports referencing ".." dir after the project directory
The full webpack config file is here
I'm probably missing something very silly, I'd be glad if someone can point it out.
I had a similar challenge and I was able to fix this by adding these definitions at the top of my webpack.config file
const babelRuntimeEntry = require.resolve('babel-preset-react-app');
const babelRuntimeEntryHelpers = require.resolve(
'#babel/runtime/helpers/esm/assertThisInitialized',
{ paths: [babelRuntimeEntry] }
);
const babelRuntimeRegenerator = require.resolve('#babel/runtime/regenerator', {
paths: [babelRuntimeEntry]
});
Then where you have the ModuleScopePlugin in the resolve.plugins
update it to be
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
babelRuntimeEntry,
babelRuntimeEntryHelpers,
babelRuntimeRegenerator])
I'm also attempting to upgrade an ejected CRA project to Webpack 5. I was able to move forward using babel-preset-react-app-webpack-5, only to encounter the next CRA-related issue.
Be sure to replace calls like require.resolve("babel-preset-react-app/dependencies") with require.resolve("babel-preset-react-app-webpack-5/dependencies").
Also, be aware the package does not appear to be production-ready, but my own project is still in early development.
I had this issue with a few other babel packages after trying to upgrade an ejected CRA app to webpack v5. I tried many different approaches some of which worked in dev but not in prod and vice versa. I found this comment in the storybook github and it was the only thing that that seemed to work in all scenarios for me.
It's kinda annoying, but by simply moving the offending packages from devDependencies in my package.json to dependencies, it seems to fix the issue. I could spend more time trying to figure out why that fixes it, but I'll leave that to someone with more free time. :)

Tree shaking of shared dependencies in webpack 5 module federation

I am working on an architecture for a dynamic dashboard with components fetched from different remote react bundles using webpack 5 module federation. I do have different libraries which are shared across some of these remote bundles. These packages are tree shakable. So each remote bundle will be having different codes from the same package. If I share these packages as singleton, when two components with same dependency loads to DOM in runtime, is there anyway webpack can get the lib code from both bundles merged? Or is it necessary that we have to disable tree shaking in such shared libraries? (By shared libraries I meant the npm packages)
Webpack automatically disables tree-shaking for shared packages.
Without being able to see exactly what you want to do I'm not exactly sure if this completely answers your question, but might be useful to the situation.
You can get more fine tuned control of bundles with modules.exports optimization.
You can get pretty granular here. A fontawesome example is at the top of the code snippet along with the optimization settings
// Import within node app
if ($('.fad').length) {
import('../../node_modules/#fortawesome/fontawesome-pro/scss/duotone.scss');
}
// Webpack
modules.exports {
optimization: {
splitChunks : {
chuncks: 'all',
cacheGroups: {
duotonecss: {
test : /[\\/]node_modules[\\/]#fortawesome[\\/]fontawesome-pro[\\/]scss[\\/](duotone)\.scss/,
name : 'duotonecss',
chunks : 'all',
enforce : true,
},
},
},
},
};

Webpack tree shaking not working between packages

Good evening!
I have been trying for a few days to get tree shaking between different packages to work.
Before going further, I have created a minimum repro that I will explain throughout this post: https://github.com/Apidcloud/tree-shaking-webpack
I have also opened an issue on webpack repo: https://github.com/webpack/webpack/issues/8951
For simplicity sake, the example just uses webpack. Babel is not used.
The above example has two packages, both with their respective bundle:
core - exports 2 functions, cube and unusedFn
consumer - imports cube from core and exports its own function, consumerFn
Core package
Note that square function is not exported in the index.js file. It's a way to know that tree shaking is indeed working within core at least, as it's not included in the final bundle (which is correct).
Consumer package
As you can see, only cube is being imported from core. It then exports its own function (consumerFn) consuming cube.
Problem
The problem is that the consumer bundle is including everything from the core bundle. That is, it's including unusedFn when it shouldn't, resulting in a bigger bundle.
Ultimately, the goal is to do the same in a monorepo with multiple packages. There's no point on having them if each package is bundling the everything from the others. The goal is to bundle only what's necessary for each package.
Using optimizationBailout I can see that ModuleConcatenation plugin is issuing some warning messages. I also used --verbose flag:
Here's my webpack.config.js:
const path = require('path');
module.exports = {
mode: 'production',
entry: {
core: './src/index.js',
consumer: './consumer/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
// for simplicity sake I removed the UMD specifics here.
// the problem is the same, with or without it.
},
optimization: {
usedExports: true,
sideEffects: true
},
stats: {
// Examine all modules
maxModules: Infinity,
// Display bailout reasons
optimizationBailout: true
}
};
I also have "sideEffects": false in the package.json.
I went through webpack's guide too, but I'm not sure what is missing.
Related issues:
webpack-3-babel-and-tree-shaking-not-working
webpack-including-unused-exports-in-final-bundle-not-tree-shaking

Understanding SplitChunksPlugin integration with Webpack

I'm fairly new to webpack and I'm looking to make some optimizations. Currently I'm using the SplitChunksPlugin to split out shared node_modules.
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
}
Currently I have two entry points
entry: { one: "./one-main.js", two: "./two-main.ts" },
So in my dist folder I now get a dev.vendors.bundle.js.
When I reload the browser, I can see that one-main.js and two.main.js have reduced in sized significantly. However i also need to include the dev.vendors.bundle.js in my html file and the size of that file offsets any savings I have made on the other two files.
My understanding (which may be wrong) is that the two bundles should have release hashes (in the file name) in prod (so will not be cached by the browser). However the content of node_modules is unlikely to change and so dev.vendors.bundle.js should not have a release hash and therefore will be cached by the browser. Is this correct or am I way off the mark? I have read several tutorials but still cannot wrap my head around it!
Any help would be appreciated.

How to bundle js library for use in browser with webpack?

I'm trying to create a minified version of a js library with webpack.
The library consists of one main function with prototypes that is exported and of several other functions it depends on that are imported in the file of the main function. This works without bundling and I assume that this file should the entry point for webpack.
I aim to bundle it into some mylib.min.js to be able access it in the browser like I would use jQuery or similar libraries. So I don't want to bundle the whole web app, just the JS library I wrote.
I'm not really getting along with it, since all tutorials show how to bundle the whole web app. My questions are:
how do I have to export the main function of the library to be able to access it in the browser?
how do I need to configure webpack?
how should I include and access the bundle in the browser?
If you could recommend any example (like tutorial, gitub repository that does this, ...) I would be happy! Any suggestions welcome!
Have you look at the documentation on the webpack website?
Here an example
For widespread use of the library, we would like it to be compatible in different environments, i.e. CommonJS, AMD, Node.js and as a global variable. To make your library available for consumption, add the library property inside output:
webpack.config.js
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
- filename: 'webpack-numbers.js'
+ filename: 'webpack-numbers.js',
+ library: 'webpackNumbers'
},
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
};
If you have any other question about that specific documentation just google webpack js authoring libraries. you'll be redirect to the good website. Website are subject to change pattern.

Categories