The Importance of locking Node module versions

Recently, a number of customers encountered build failures in their JavaScript-based Liferay portlets or modules. The issue was the result of a node module version not being pinned or specified. My colleague, Dave Nebinger provides full details of this issue in this article. The issue highlights the importance of locking versions of node modules and what can happen when you do not. However, I thought this would be a good time to review some best practices and mention the future of the liferay-npm-bundler. First, a big thanks to my colleague, Leandro Aguiar who uncovered what the issue was and helped bring about a quick resolution.  

 

Summary of recent issue:

For context, let’s quickly review the recent issue that occurred. All of sudden customers discovered that one day a build that used to succeed was now failing without any code changes. The build logs contained lines with "ENOENT" errors, often related to missing files or directories. Sometimes the error said Object.readFileSync, other times it was Object.writeFileSync. 

For example the errors experienced were all similar to the following:

Error: ENOENT: no such file or directory, open '/var/jenkins_home/workspace/web_master/liferay/modules/foo-react-app/build/node_modules/loose-envify@1.4.0/cli.js'

  at Object.openSync (fs.js:438:3)

  at Object.readFileSync (fs.js:343:35)

  at loadSourceMap (/var/jenkins_home/workspace/web_master/liferay/modules/foo-react-app/node_modules/liferay-npm-bundler/lib/steps/transform.js:208:44)

  at Promise (/var/jenkins_home/workspace/web_master/liferay/modules/foo-react-app/node_modules/liferay-npm-bundler/lib/steps/transform.js:168:29)

 

Root Cause:

The result of the investigation revealed that a recent update to the fast-glob package to version 3.3.3 had introduced a breaking change that broke how the liferay-npm-bundler operates. This change impacted Liferay workspaces that did not have properly configured lockfiles (package-lock.json for npm and yarn.lock for yarn). Without the proper lock files the build process will generate them on the fly using the latest compatible version of all dependencies e.g. globby or fast-glob, which would lead to the observed errors. I have seen this cause issues many times in the past with customer projects where builds suddenly break.

To fix this specific issue, we ultimately released a new version of our liferay-npm-bundler tool, version 2.32.2. However, this brings up another point regarding the future of the bundler. As of Liferay 2024.Q4/7.4 GA129, the liferay-npm-bundler has been deprecated. Therefore, customers should consider moving to the replacement, since updates to it will be coming to an end. 

 

Replacement for liferay-npm-bundler:

The recommended replacement is for customers to transition to either using Custom Element or Iframe Client Extensions. These allow for greater flexibility by allowing a level of insulation or decoupling, which OSGI javascript portlets/modules do not. For example, with these client extensions you are no longer restricted to the React version that Liferay ships and are free to leverage whatever version your organization desires. Also, these types of client extensions allow you to use whatever the native tooling is for the frontend framework you choose, e.g. React, Angular, etc... This means your frontend build dependencies no longer have to include liferay specific npm modules. However, even when using Custom Element or IFrame Client Extensions, best practices for locking node modules should still be applied.  

 

Best Practices for Locking Node Module Versions:

Again, this incident highlights the critical importance of carefully managing Node package versions for ensuring build stability and unexpected regressions. Below are some best practices to consider:

  1. Utilize Lockfiles:

    • Always commit lockfiles (package-lock.json for npm and yarn.lock for yarn). Lockfiles capture the exact versions of all dependencies, including transitive dependencies, ensuring consistent installations across different environments. Use npm install or yarn install to update the lockfile before committing it.

    • Communicate with your team which package manager should be used (npm or yarn) to avoid having mixed lockfiles in the workspace.

  2. Version Range Restrictions:

    • In your package.json files, define specific version ranges for your dependencies using semantic versioning (e.g., ^1.2.3, ~2.0.0). This allows for minor or patch updates while preventing major version upgrades that could introduce breaking changes.

  3. Regularly Update Dependencies:

    • While pinning versions is crucial to ensure build stability, it's equally important to periodically update your dependencies to receive security patches, bug fixes, and performance improvements.

    • Establish a regular process for reviewing and updating dependencies, such as a weekly or monthly schedule.

    • Explore using dependency management tools like Dependabot and Renovate. These can automatically create pull requests to update dependencies based on your defined rules, making it easier to stay up-to-date.

  4. Thorough Testing:

    • After updating dependencies, always perform thorough testing in a staging or development environment before deploying to production. This helps identify and mitigate any potential issues early on.

  5. Monitor for Security Vulnerabilities:

    • Regularly scan your dependencies for known security vulnerabilities using tools like npm audit or yarn audit or automated services

By implementing these best practices, you can significantly improve the stability and maintainability of your javascript projects while minimizing the risk of unexpected build failures caused by dependency issues.

Blogs

Hey Chris Mount,       Hello,We are developing a React portlet using the Adapt approach. It was functioning properly until recently, but we have encountered the following error during deployment for the past two days. Deployment Command: npm run deploy:liferay Error Details: Error: EMFILE: too many open files, open 'D:\workspace-liferay\portlets\portlet-test\build.liferay\jar\node_modules\jest-circus@27.5.1\runner.js' at Object.openSync (node:fs:573:18) at Object.readFileSync (node:fs:452:35) at loadSourceMap (D:\workspace-liferay\portlets\portlet-test\node_modules\liferay-npm-bundler\lib\steps\transform.js:208:44) at D:\workspace-liferay\portlets\portlet-test\node_modules\liferay-npm-bundler\lib\steps\transform.js:168:29 Can you help us in this regards?