Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk): Build
@dotcms/client
in commonjs and esmodule (#29699)
##⚠️ If you are not familiar with the JavaScript Module Ecosystem, scroll down to the FAQs section ## What happen? TLDR: The library was not working with some frameworks, example: Astro, Nodejs or Nextra (and probably others) because they wanted commonjs and didn't have that. ## What did I do? I transform our library into a “dual-package” library, that supports both CommonJS and ES Modules. ## Is 2024 why do we need this? Under certain conditions, Astro, Nextra, Nodejs and probaly others require CommonJS. ## What conditions? Imagine your library `package.json` looks like this: ```json { "name": "@dotcms/test-123", "version": "0.0.1", "dependencies": {}, "main": "./index.cjs.js", "module": "./index.esm.js", "types": "./index.esm.d.ts" } ``` For this, Astro will look for the `main` field and use `index.cjs.js` as the entry point in the server. ``` --- import { test123 } from '@dotcms/test-123'; console.log(test123()); --- ``` But in the browser, Astro will look for the `module` field and use `index.esm.js` as the entry point. ```html <script> import { test123 } from '@dotcms/test-123'; console.log(test123()); </script> ``` Now we add `"type": "module"` to the `package.json`: ``` { "name": "@dotcms/test-123", "version": "0.0.1", "type": "module", "dependencies": {}, "main": "./index.cjs.js", "module": "./index.esm.js", "types": "./index.esm.d.ts" } ``` **Astro fails to build:** ``` 17:04:27 [ERROR] exports is not defined in ES module scope This file is being treated as an ES module because it has a '.js' file extension and '/Users/fmontes/Developer/@deleteme/test-astro/node_modules/@dotcms/test-123/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension. ``` And this is because both files are `.js` and Astro (well node resolution strategy) will try to load the `.js` file as an ES module but the `main` is the commonjs one. So it doesn't matter if we have `"module": "./index.esm.js"` it will go to the `"main": "./index.cjs.js"`. If instead the `package.json` looked like this: ```json { "name": "@dotcms/test-123", "version": "0.0.1", "type": "module", "main": "./index.cjs", "module": "./index.mjs", "types": "./index.d.ts" } ``` It would work because the file extensions are `.cjs` and `.mjs`. You will see more of this in the [Node module resolution strategy.](#Node-module-resolution-strategy) Another apporach to make it works is if we use `exports` because we can specify the entry point for `import` and `require`: ```json { "name": "@dotcms/test-123", "version": "0.0.1", "main": "./index.cjs.js", "module": "./index.esm.js", "type": "module", "exports": { ".": { "types": "./index.esm.d.ts", "import": "./index.esm.js", "require": "./index.cjs.js" } } } ``` This will work too because we are specifying the entry point for `import` (esmodule) and `require` (commonjs). ## Node module resolution strategy Strategy that determines how Node.js finds and loads modules. ### First node look for modules in the following order: 1. Core modules 2. Local modules (starting with `'./'` or `'../'`) 3. `node_modules` directories (starting from the current directory and moving up) ### File extensions: - `.js`, `.cjs.js`, `.mjs.js` node think it can be CommonJS or ES module, only care the real extension. - `.cjs` node think it is CommonJS - `.mjs` node think it is ES module ### `type` property in the `package.json`: - `"type": "module"`: Node think all `.js` files are ES modules. - If no `type` is provided node thinks all `.js` files are CommonJS. ### `"exports"` in the `package.json`: - Defines entry points for the package. - Can specify different entry points for different environments (e.g., browser vs. Node.js) - Takes precedence over `"main"` and `"module"` fields. ## Dual package are @#$#$% hard My proposal is to provide a dual package that works with all the frameworks. In order to do this I leverage the `exports` property. So `@dotcms/client` `package.json` used to look like this: ```json { "name": "@dotcms/client", "version": "0.0.1-alpha.31", "type": "module", "module": "./src/index.js", "main": "./src/index.js", "types": "./src/index.d.ts" } ``` And now it looks like this: ```json { "name": "@dotcms/client", "version": "0.0.1-alpha.32", "exports": { "./package.json": "./package.json", ".": { "module": "./index.esm.js", "types": "./index.esm.d.ts", "import": "./index.cjs.mjs", "default": "./index.cjs.js" } }, "module": "./index.esm.js", "main": "./index.cjs.js", "types": "./index.esm.d.ts" } ``` And this is pretty much it, hope you find it useful. ## FAQs: Understanding the JavaScript Ecosystem and Module Systems ### 1. **What is a JavaScript Module?** A JavaScript module is a reusable piece of code that you can import into other JavaScript files. Modules help keep code organized and maintainable by breaking it into smaller, manageable parts. There are two main module formats in JavaScript: - **CommonJS (CJS)**: Traditionally used in Node.js. Modules are loaded synchronously using `require`. - **ES Modules (ESM)**: A newer standard that allows you to use `import` and `export` syntax. This is increasingly used in modern JavaScript environments, especially in browsers. ### 2. **What is Astro?** Astro is a modern web framework designed for building fast, content-focused websites. It allows developers to use components from different frameworks (like React, Vue, etc.) while focusing on delivering optimized, static sites. Astro automatically determines the most efficient way to render pages, which sometimes requires CommonJS modules. ### 3. **Why do we need CommonJS (CJS) in 2024?** Even though ES Modules are the future of JavaScript, some frameworks (like Astro, Node.js, and others) still require CommonJS under certain conditions, especially on the server-side. This is because Node.js has long supported CommonJS, and many tools and libraries still rely on it. ### 4. **What is a "dual-package" library?** A dual-package library is a package that supports both CommonJS (CJS) and ES Modules (ESM). This allows the same library to work across different environments, such as older Node.js versions (which may need CJS) and modern JavaScript environments (which prefer ESM). ### 5. **Why do we need a "dual-package" library?** To ensure compatibility across various frameworks and environments. Some tools expect CJS modules, while others expect ESM modules. A dual-package library can satisfy both requirements, preventing compatibility issues during builds or runtime. ### 6. **What is the `package.json` file?** The `package.json` file is a configuration file in JavaScript projects that defines the project metadata (e.g., name, version, dependencies) and how the package is structured. It also specifies the entry points (`main`, `module`, `exports`) that tell Node.js or the browser which files to load. ### 7. **What is the `exports` field in `package.json`?** The `exports` field is a more modern way to define entry points in a package. It allows specifying different entry points for different environments (e.g., browser vs. Node.js, or ES Module vs. CommonJS). This field takes precedence over `main` and `module` and helps avoid issues when resolving modules. ### 8. **What is Node.js?** Node.js is a runtime environment that allows you to run JavaScript on the server. It's widely used for building backend services, tools, and even desktop applications. Node.js primarily used CommonJS in the past, but it now supports ES Modules as well. ### 9. **What is the problem this pull request solves?** The pull request addresses an issue where the library wasn't compatible with some frameworks (like Astro) because it didn't support CommonJS modules. By transforming the library into a dual-package that supports both CommonJS and ES Modules, it ensures compatibility across a wider range of environments. ### 10. **What is Rollup?** Rollup is a JavaScript module bundler that compiles small pieces of code into something larger and more complex, like a library or application. It's particularly good at bundling ES Modules and is used to generate both CommonJS and ES Module versions of a library. ### 11. **What changes were made to the build system?** The build system was updated to use Rollup, which allows us to generate both CommonJS and ES Module versions of the library. Additionally, the `package.json` was updated with the `exports` field to clearly define entry points for different environments. ### 12. **Why did Astro fail to build before?** Astro failed to build because it was trying to load a CommonJS file (`index.cjs.js`) as an ES Module due to the way Node.js resolves modules. By adding specific file extensions and using the `exports` field in `package.json`, we can avoid this issue and ensure Astro loads the correct module format. -------------------------------- ### Proposed Changes * Update the build system to rollup * Add `exports` to package.json ### Checklist - [x] Tests - [x] Translations - [ x] Security Implications Contemplated (add notes if applicable) ### Additional Info ** any additional useful context or info ** ### Screenshots Original | Updated :-------------------------:|:-------------------------: ** original screenshot ** | ** updated screenshot **
- Loading branch information