One project with multiple package.json files - node.js

I'm relatively new to modern JS development and I need help or advice about this situation I'm in.
Situation: We have a React-Typescript-Redux project supporting IE8 (React 0.14). Now we're upgrading to IE11 and React 16 but IE8 should be supported.
Requirement: Reduce project maintenance between browser versions by using different packages and/ or config files for each build.
Problem: From research I made so far it seems impossible to use different package.json files and node_modules folders inside the same project with selected tools: npm, Webpack, React, Redux, Typescript. Yarn seems to support multiple package.json files but we'd like to avoid migrating from npm if possible.
Current project structure:
project_root/
node_modules/
src/
components/
utils/
index.tsx
components.css
index.html
package.json
tsconfig.json
webpack.config.json
What I thought might work was to introduce IE8 subfolder with its package.json and node_modules folder and then reference that folder for the build task somehow but now I'm oblivious how to tell npm to reference it on build.
Proposed project structure:
project_root/
ie8/
node_modules/
package.json
node_modules/
src/
components/
utils/
index.tsx
components.css
index.html
package.json
tsconfig.json
webpack.config.json
I tried different things found on web, including resolve.modules: [__dirname + "/ie8/node_modules"] but it seems it doesn't work or I misunderstand what it does because I get Cannot find name 'require' errors on several files and Typescript 2.8.3 is referenced in terminal output instead 2.3.4. Without it, project builds with configuration for IE11.
So, can anybody tell me with certainty it's not possible or offer some guidance? This is the closest answer I found so far but doesn't sound final. Alternatively, can project structure like this support what is required or separating project into two is the best bet?
Thanks in advance.

OK, so after some more research I stumbled upon Lerna which mostly allows me to do what I wanted (from what I've seen so far). It requires specific project tree setup, like this:
project_root/
node_modules/
packages/
components/ // Components shared between projects
components/
MyComponent.jsx
index.jsx
legacy/
output/
build.js // React 0.14 build
node_modules/
package.json // project specific dependencies
index.jsx // project specific entry
.babelrc
modern/
output/
build.js // React 16 build
node_modules/
package.json // project specific dependencies
index.jsx // project specific entry
.babelrc
package.json // contains devDependencies shared between projects
lerna.json
webpack.config.js
index.html
Then, in components/index.jsx I specified require commands for different versions based on global variable:
if(PROJECT_SRC == "legacy"){
React = require('../legacy/node_modules/react');
ReactDOM = require('../legacy/node_modules/react-dom');
} else {
React = require('../modern/node_modules/react');
ReactDOM = require('../modern/node_modules/react-dom');
}
Note: This is probably bad practice but the only way at the moment I could include different React versions in the build. I'll have to see what problems arise with this approach after the whole project changes to this model.
In webpack.config.js I configured two exports - one for modern and one for legacy. Each points to a different entry index.jsx file, uses webpack.DefinePlugin to set global variable to "legacy" or "modern", and specifies path to common components module to resolve: ['node_modules', path.resolve(__dirname, 'components')]
webpack.config for a single project output looks something like this:
{
entry: "./packages/legacy/index.jsx",
target: "web",
output:
{
filename: "build.js",
path: __dirname + "/packages/legacy/dist/",
libraryTarget: "var",
library: "lib_name"
},
devtool: "source-map",
resolve: {
extensions: [".js", ".jsx", ".json"],
modules: ['node_modules', path.resolve(__dirname, 'components')]
},
plugins: plugins_legacy,
module: {
loaders: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/
}
]
}
}
Feel free to comment or point to problems but I hope this will help somebody in the future! :)

Related

Class extends value #<Object> is not a constructor or null when reconstructing node module hierarchy

I have this problem :
I have this Directories Hierarchy:
Main-Directory
-SubProject1
-Package.json
-DirectoryA
-DirectoryB
-Package.json
-DirectoryC
-Package.json
-SubProject2
-Package.json
and to avoid having many node_modules and dependencies I decided to use Yarn workspaces first I tried with the Subproject1 directory (I considered it as a monorepo) and everything installed in its node-modules and the project is working fine. I know yarn workspaces doesn't support nested workspaces so I have to put in the SubProject1/package.json:
"workspaces": {
"packages": ["DirecotryA/DirectoryB/DirectoryC", "DirectoryA/*]
}
then I wanted to do the same for the whole project so I created package.json in the Main Directory and added
"workspaces": {
"packages": ["SubProject1/DirectoryA/DirectoryB/DirectoryC" , "SubProject1/DirectoryA/*", "./SubProject1", "./SubProject2"]
}
and all the node modules are installed in the node_modules folder in the main directory (except for some dependencies with different versions)
now after I run my project I got:
Class extends value # is not a constructor or null
even though no change was made to the code.
So my question is:
could rebuilding the hierarchy of the node modules lead to this kind of errors? I searched and I found out that there might be a cycled dependency, but I didn't change anything except for using yarn workspaces
So does anyone knows why this could happen?

Importing typescript files for Angular project in node server

I am unable to import TS files that exist in my Angular project directories inside my Node server.
I've looked into various settings for a tsconfig.json file for the Node server specifically but have had no luck.
I run my node server via npm start like so "start": "nodemon --exec ts-node -- ./start.ts"
My project structure looks something like this...
....
node_modules/
src/
app/
shared/
models/
entity.model.ts
stub-server/
start.ts
src/
data/
entity-data.ts
angular.json
tsconfig.json
...
I expect to be able to import the relevent Typescript classes/interfaces/enums etc from entity.model.ts inside entity-data.ts so that I can enforce type safety within my mocked data.
You definitely need to use better tooling for your project.
Please check https://nx.dev/ and create your Angular app and Node app, move your code there, and then you will be able to create a library with your shared stuff, to be imported from both projects.
Nx will take care of the configuration and the tsconfig paths required to make it work. And talking about tsconfig.paths, you may try to setup one in your current project, perhaps this structure is not clean:
"compilerOptions": {
"paths": {
"#shared/*": ["src/app/shared/*"]
}
}
and import your stuff from your server like
import { MyModel } from '#shared/models';
Anyways, consider the migration of your code to Nx and you will be able to enjoy a clean architecture and workflow. Happy coding!

Get version number from package.json in React Redux (create-react-app)

OP EDIT: If anyone else comes across this: the app was created using create-react-app, which limits importing to within the src folder. However if you upgrade react-scripts to v1.0.11 it does let you access package.json.
I'm trying to get the version number from package.json in my app.
I've already tried these suggestions, but none of them have worked as I can't access package.json from outside the src folder (might be due to React, I'm new to this). Moving package.json into src then means I can't run npm install, npm version minor, and npm run build from my root folder. I've tried using process.env.npm_package_version but that results in undefined.
I'm using Jenkins, and I haven't set it up to push the commits up yet, but the only idea I have is to get the version from the tags in GitLab, but I have no idea how to do that, and it would add unnecessary dependency to the repo, so I would really like to find an alternative.
EDIT:
My file structure is like:
--> RootAppFolder
|--> build
|--> node_modules
|--> public
|--> src
|--> Components
|--> Root.js
|
|--> package.json
So to access package.json from Root.js I have to do import packageJson from './../../package.json' and then I get the following error:
./src/components/Root.js
Module not found: You attempted to import
./../../package.json which falls outside of the project src/
directory. Relative imports outside of src/ are not supported. You can
either move it inside src/, or add a symlink to it from project's
node_modules/.
Solving this without importing and exposing package.json to the create-react-app
Requires: version 1.1.0+ of create-react-app
.env
REACT_APP_VERSION=$npm_package_version
REACT_APP_NAME=$npm_package_name
index.js
console.log(`${process.env.REACT_APP_NAME} ${process.env.REACT_APP_VERSION}`)
Note: the version (and many other npm config params) can be accessed
Note 2: changes to the .env file will be picked only after you restart the development server
From your edit I would suggest to try:
import packageJson from '/package.json';
You could also try to create a symlink:
# From the project root.
cd src; ln -s ../package.json package.alias.json
List contents of src directory and you'll see the symlink.
ls
#=> package.alias.json -> ../package.json
Adding the .alias helps reduce the "magic" for others and your future self when looking at this. Plus, it'll help text editors keep them apart. You'll thank me later. Just make sure you update your JS code to import from ./package.alias.json instead of ./package.json.
Also, please take a look at this question:
The create-react-app imports restriction outside of src directory
Try this.
// in package.json
"version": "1.0.0"
// in index.js
import packageJson from '../package.json';
console.log(packageJson.version); // "1.0.0"
I don't think getting version by 'import' or 'require' package is correct.
You can add a script in you package.json
"start": "REACT_APP_VERSION=$npm_package_version react-app-script start",
You can get it by "process.env.REACT_APP_VERSION" in any js files.
It also works in build scripts, like this:
"build": "REACT_APP_VERSION=$npm_package_version react-app-script build",
import package.json
Generally speaking, importing package.json is not good. Reasons: security & bundle size concerns
Yes, latest webpack (default config) + ES6 import does tree-shaking (i.e. only includes the "version" value instead of the whole package.json) for both import packageJson from '../package.json' and import { version } from '../package.json'. But it is not guaranteed if you use CommonJS (require()), or have altered your webpack config, or use another bundler/transpiler. It's weird to rely on bundler's tree-shaking to hide your sensitive data. If you insist on importing package.json but do not want the whole package.json exposed, you may want to add some post-build checks to ensure other values in package.json are removed.
However the security concern here remains theoretical for open source projects whose package.json is public after all. If both security and bundle size are not problems, or, the non-guaranteed tree-shaking is good enough for you, then go ahead)
.env
The .env method, if it works, then it's good, but if you don't use create-react-app, you might need to install dotenv and do some additional configurations. There's also one small concern: it is not recommended to commit the .env file (here and here), but if you do the .env method, it looks like you will have to commit the file as it likely becomes essential for your program to work.
Best practice (arguably)
(this is not primarily for create-react-app, but you still can either use react-app-rewired or eject cra in order to configure webpack in cra)
If you use webpack, then with DefinePlugin:
plugins: [
new webpack.DefinePlugin({
'process.env.VERSION': JSON.stringify(
process.env.npm_package_version,
),
}),
]
You can now use console.log(process.env.VERSION) in your front-end program (development or production).
(You could simply use VERSION instead of process.env.VERSION, but it usually requires additional configuration to satisfy linters: add globals: {VERSION: 'readonly'} in .eslintrc (doc); add declare var VERSION: string; in .d.ts file for TypeScript)
Although it's "npm_package_version", it works with yarn too. Here's a list of npm's exposed environment variables.
Other bundlers may have similar plugins, for example, #rollup/plugin-replace.
Open your package.json file and change this line
Problem Is:
// in package.json
"scripts": {
"dev": "REACT_APP_VERSION=local REACT_APP_VERSION_NUMBER=$npm_package_version react-scripts start",
...
}
Solution is
// in package.json
"scripts": {
"dev": "react-scripts start",
...
}

Aurelia multi project build with Webpack

I'm using Aurelia and the new aurelia-webpack-plugin (2.0.0-rc.2). I want to use this as a "multiproject build":
.
├── project1
├── project2
├── project3
├── build
The three projects are all using Aurelia (same versions), but are independent. To avoid build script duplicates I extracted the webpack.config.js and several other scripts to the build folder. The projects are calling them via npm scripts from the package.json of the project. The working directory of this node-process is changed to the build folder before webpack is called.
Some relevant configs in my webpack config:
...
new AureliaPlugin({
includeAll: path.resolve(`${projectDir}/app/`),
viewsFor: `${path.resolve(projectDir)}/app/**/*.{ts,js}`
})
...
entry: {
main: "aurelia-bootstrapper"
},
resolve: {
modules: [`${projectDir}/app`, `${projectDir}/node_modules`, "node_modules"]
}
...
projectDir points to the specific root directory of the project.
The problem is now, that the GlobDependenciesPlugin inside the aurelia-webpack-plugin does not find the entrypoints (Main.ts) of the projects.
After debugging I found at least two reasons for this:
this.root in GlobDependenciesPlugin points to the current cwd of the node process, which is the build folder (there is no way to configure the value)
only the last "node_modules" folder in the modules array from the config is used for searching. The others are filtered out, cause of relative paths.
Is there a way to get this working with this shared build script structure?

how to share code between Webpack build and NodeJS server process?

My application has a directory structure more or less like this:
src-program/ - contains frontend code including package.json and webpack.config.js
src-server/ - contains backend code including a different package.json and .babelrc
shared/foo.js - is JavaScript code that is needed by both the frontend and the backend
All code uses ES2015 syntax and thus is transpiled using Babel.
For the frontend the "transpilation" is done during the Webpack build by using the babel-loader.
For the backend it is done on-the-fly by babel-register.
shared/foo.js requires other modules, that are found in the package.json files of both the frontend and the backend.
Due to how NodeJS/Webpack resolve modules, the shared module isn't found normally.
For Webpack I solved this in a somewhat hacky way using this configuration:
resolve: {
root: __dirname,
fallback: [
__dirname + "/../shared",
__dirname + "/node_modules"
],
extensions: ['', '.js', '.jsx']
},
The first fallback makes sure that the "shared" module is resolved and the second fallback makes sure that modules required by the shared module are still resolved to the frontend node_modules directory.
This allows including the shared module as simple as this:
import * as foo from 'foo';
However, I'm having difficulties to make the backend (ie. NodeJS) resolve the shared module the same way.
I tried with app-module-path, which makes foo.js resolve, but then the file is either not processed by Babel or additional Babel modules like transform-runtime (indirectly needed by foo.js) cannot be resolved since they reside in src-server/node_modules...
I could probably work around the problem by pre-transpiling the code instead of using babe-register but it all doesn't feel right anyway.
So, what is a good way to share code between a Webpack build and the NodeJS server process?
Can you package the shared module up as an npm package (even a package just residing on your filesystem)? Then your src-program and src-server projects can add it as a dependency in their package.json, and it will get copied into their respective node_modules folders.
See: how to specify local modules as npm package dependencies

Resources