Skip to content

Commit 8685d2c

Browse files
authored
[WTF-2074] Rollup 3 Upgrade (#109)
## Checklist - Contains unit tests ✅ ❌ - Contains breaking changes ✅ - Compatible with: MX 🔟 - Did you update version and changelog? ✅ - PR title properly formatted (`[XX-000]: description`)? ✅ ## This PR contains - [ ] Bug fix - [ ] Feature - [x] Refactor - [x] Documentation - [ ] Other (describe) ## What is the purpose of this PR? This PR aims to upgrade the bundler setup in order to offer more recent packages and extend the longevity of the package. ## Relevant changes Package versions have been updated and modifications have been made to the Rollup and jest configurations to be compatible with newer versions. ## What should be covered while testing? How to test the branch locally: 1. Pull repository and checkout this branch `wtf/rollup-3` 2. Run `npm pack` 3. Copy the full path of the resulting archive (The previous command states the name of the file) 4. In the widget directory, run `npm install $full_path_to_archive` Properties to check: - Newly generated widgets should build and test without any modification necessary. - Widgets without custom configuration should build and test without any modification necessary. - Widgets with custom configuration (for example [carousel-web](https://github.com/mendix/web-widgets/blob/main/packages/pluggableWidgets/carousel-web/rollup.config.js)) should be upgrade-able using the guide in CHANGELOG.md ## Extra comments (optional)
2 parents 542f6da + 3383eff commit 8685d2c

15 files changed

Lines changed: 2485 additions & 2366 deletions

packages/pluggable-widgets-tools/CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,65 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
#### Rollup 3
12+
13+
We upgraded the bundling setup. For most packages this meant updating to the latest minor or patch versions, but for Rollup we upgraded from major version 2 to 3. If your widget uses custom configurations you may need to perform some actions.
14+
15+
1. Ensure you are using node 20 or above.
16+
2. Upgrade any Rollup related package to their latest (Rollup 3 supporting) version: `npm install <package-name>@latest`.
17+
3. Update your custom rollup configuration:
18+
- If your rollup configuration imports our base rollup configuration, rename the import to `@mendix/pluggable-widgets-tools/configs/rollup.config.mjs`.
19+
- Rollup now offers native support for ES Modules for Rollup configuration. This does mean it is stricter with regards to ESM and CJS, ensure that:
20+
- If you are using ESM to use the `.mjs` extension for your `rollup.config.mjs` file.
21+
- If you are using CJS you can continue using the `.js` extension for your `rollup.config.js` file.
22+
23+
If you decide to continue using ES Modules for your rollup configuration, there are [some caveats to be aware of](https://rollupjs.org/command-line-interface/#caveats-when-using-native-node-es-modules). Below we highlight a few.
24+
25+
##### ES Module Imports
26+
27+
When using ESM you should rename your Rollup configuration files to `.mjs`. This tells node to expect ESM for that particular file. Import statements targeting local files should be updated to include the file extension.
28+
29+
When importing some CJS packages you may no longer be able to access individual named exports directly. In this case you will need to import the full module. You can [desctructure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) the module to keep the rest of your implementation the same.
30+
31+
```js
32+
// Partial import
33+
import { cp } from "shelljs";
34+
// Full namespace import
35+
import shelljs from "shelljs";
36+
const { cp } = shelljs;
37+
```
38+
39+
##### ES Module __Dirname
40+
41+
In ESM files the `__dirname` variable is not available. Instead, you can access the current file's path using `import.meta.url`. Do note that this includes the `file:` protocol prefix. An easy way to work with the file's url is to use it as the [base value for the URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL#base). This will allow you to resolve a relative path from that path. For example `new URL("../", "file:/a/b/c.txt")` results in the URL `file:/a`.
42+
43+
```js
44+
// ESM equivalent of __dirname
45+
const dirname = new URL("./", import.meta.url).pathname;
46+
```
47+
48+
Also note that `require()` is unavailable in ESM files. A common usecase for rollup setups is accessing the package.json of a project. If this is a static location relative to the script, you may use a typed import. Otherwise, read the file from the file system and parse it as JSON.
49+
50+
```js
51+
// CJS method
52+
import { join } from "node:path";
53+
const packagePath = join(process.cwd(), "/package.json");
54+
const package = require(packagePath);
55+
56+
// ESM import with attributes
57+
import package from "../package.json" with { type: "json" }
58+
59+
// Dynamic import with attributes
60+
const package = await import(packagePath, { with: { type: "json" }})
61+
62+
// Read file and parse (async version with fs.readFile)
63+
import { readFileSync } from "node:fs"
64+
const package = JSON.parse(readFileSync(packagePath))
65+
```
66+
67+
968
## [10.16.0] - 2024-10-31
1069
1170
### Added

packages/pluggable-widgets-tools/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ In your `package.json` scripts, use the following command with the desired task:
6767
- `.eslint.js` - configuration for ESLint. We recommend to just re-export `@mendix/pluggable-widgets-tools/configs/eslint.ts.base.json`
6868
- `prettier.config.js` - configuration for Prettier. We recommend to just re-export `@mendix/pluggable-widgets-tools/configs/prettier.base.json`
6969
- `tsconfig.json` - configuration for TypeScript. We recommend to just extend `@mendix/pluggable-widgets-tools/configs/tsconfig.base.json`
70-
- `rollup.config.js` - (optional) custom configurations for [rollup](https://rollupjs.org/guide/en/) bundler. The standard configuration is passed as an argument named `configDefaultConfig`.
70+
- `rollup.config.[m]js` - (optional) custom configurations for [rollup](https://rollupjs.org/guide/en/) bundler. The standard configuration is passed as an argument named `configDefaultConfig`.
7171
- `package.json` - widget package definitions, including its dependencies, scripts, and basic configuration (`widgetName` and `config.projectPath` in particular)
7272

7373
## Migrating from previous versions
7474

75+
- 10.17 includes an upgrade from Rollup 2 to 3. See the [changelog](./CHANGELOG.md) for upgrade notes.
7576
- Webpack bundler is changed to a Rollup. You must migrate your custom configuration.
7677
- Update `pluggable-widgets-tools` commands used in your `package.json` file to one of the described in this readme. In particular `start:js`, `start:ts`, and `start:server` commands should be changed to `start:web`.
7778
- You now can use named exports in your widget. That is, you can write `export MyWidget;` instead of `export default MyWidget;`.

packages/pluggable-widgets-tools/bin/mx-scripts.js

100644100755
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ checkNodeVersion();
2424
const realCommand = getRealCommand(cmd, toolsRoot) + " " + args.join(" ");
2525
console.log(`Running MX Widgets Tools script ${cmd}...`);
2626

27+
const nodeModulesBins = findNodeModulesBin();
2728
for (const subCommand of realCommand.split(/&&/g)) {
2829
const result = spawnSync(subCommand.trim(), [], {
2930
cwd: process.cwd(),
3031
env: {
3132
...process.env,
32-
PATH: `${process.env.PATH}${delimiter}${findNodeModulesBin()}`,
33+
PATH: [process.env.PATH].concat(nodeModulesBins).join(delimiter),
3334
// Hack for Windows using NTFS Filesystem, we cannot add platform specific check otherwise GitBash or other linux based terminal on windows will also fail.
34-
Path: `${process.env.Path}${delimiter}${findNodeModulesBin()}`
35+
Path: [process.env.Path].concat(nodeModulesBins).join(delimiter),
3536
},
3637
shell: true,
3738
stdio: "inherit"
@@ -47,8 +48,8 @@ function getRealCommand(cmd, toolsRoot) {
4748
const prettierConfigRootPath = join(__dirname, "../../../prettier.config.js");
4849
const prettierConfigPath = existsSync(prettierConfigRootPath) ? prettierConfigRootPath : "prettier.config.js";
4950
const prettierCommand = `prettier --config "${prettierConfigPath}" "{src,typings,tests}/**/*.{js,jsx,ts,tsx,scss}"`;
50-
const rollupCommandWeb = `rollup --config "${join(toolsRoot, "configs/rollup.config.js")}"`;
51-
const rollupCommandNative = `rollup --config "${join(toolsRoot, "configs/rollup.config.native.js")}"`;
51+
const rollupCommandWeb = `rollup --config "${join(toolsRoot, "configs/rollup.config.mjs")}"`;
52+
const rollupCommandNative = `rollup --config "${join(toolsRoot, "configs/rollup.config.native.mjs")}"`;
5253

5354
switch (cmd) {
5455
case "start:web":
@@ -105,14 +106,18 @@ function getRealCommand(cmd, toolsRoot) {
105106

106107
function findNodeModulesBin() {
107108
let parentDir = join(__dirname, "..");
109+
const bins = [];
108110
while (parse(parentDir).root !== parentDir) {
109111
const candidate = join(parentDir, "node_modules/.bin");
110112
if (existsSync(candidate)) {
111-
return candidate;
113+
bins.push(candidate);
112114
}
113115
parentDir = join(parentDir, "..");
114116
}
115-
throw new Error("Cannot find bin folder");
117+
if (bins.length === 0) {
118+
throw new Error("Cannot find bin folder");
119+
}
120+
return bins;
116121
}
117122

118123
function checkNodeVersion() {

packages/pluggable-widgets-tools/configs/common/glob.js renamed to packages/pluggable-widgets-tools/configs/common/glob.mjs

File renamed without changes.

packages/pluggable-widgets-tools/configs/helpers/rollup-helper.js renamed to packages/pluggable-widgets-tools/configs/helpers/rollup-helper.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import { existsSync, mkdirSync } from "fs";
44
import { join } from "path";
55
import fg from "fast-glob";
6-
import { cp } from "shelljs";
7-
import { zip } from "zip-a-folder";
8-
import { LICENSE_GLOB } from "../common/glob";
6+
import shelljs from "shelljs";
7+
import zipAFolder from "zip-a-folder";
8+
import { LICENSE_GLOB } from "../common/glob.mjs";
9+
10+
const { cp } = shelljs;
11+
const { zip } = zipAFolder;
912

1013
export async function copyLicenseFile(sourcePath, outDir) {
1114
const absolutePath = join(sourcePath, LICENSE_GLOB);

packages/pluggable-widgets-tools/configs/rollup-plugin-assets.js renamed to packages/pluggable-widgets-tools/configs/rollup-plugin-assets.mjs

File renamed without changes.

packages/pluggable-widgets-tools/configs/rollup-plugin-collect-dependencies.js renamed to packages/pluggable-widgets-tools/configs/rollup-plugin-collect-dependencies.mjs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/explicit-function-return-type */
22

33
import fg from "fast-glob";
4-
import { readJson, writeJson } from "fs-extra";
4+
import fsExtra from "fs-extra";
55
import { existsSync, readFileSync, writeFileSync } from "fs";
66
import { dirname, join, parse } from "path";
77
import copy from "recursive-copy";
@@ -10,7 +10,9 @@ import resolve from "resolve";
1010
import _ from "lodash";
1111
import moment from "moment";
1212
import mkdirp from "mkdirp";
13-
import { LICENSE_GLOB } from "./common/glob";
13+
import { LICENSE_GLOB } from "./common/glob.mjs";
14+
15+
const { readJson, writeJson } = fsExtra;
1416

1517
const dependencies = [];
1618
export function collectDependencies({
@@ -115,7 +117,7 @@ async function resolvePackage(target, sourceDir, optional = false) {
115117
if (
116118
e.message.includes("Cannot find module") &&
117119
!/\.((j|t)sx?)|json|(pn|jpe?|sv)g|(tif|gi)f$/g.test(targetPackage) &&
118-
!/configs\/jsActions/i.test(__dirname) && // Ignore errors about missing package.json in 'jsActions/**/src/*' folders
120+
!/configs\/jsActions/i.test(import.meta.url) && // Ignore errors about missing package.json in 'jsActions/**/src/*' folders
119121
!optional // Certain (peer)dependencies can be optional, ignore throwing an error if an optional (peer)dependency is considered missing.
120122
) {
121123
throw e;

packages/pluggable-widgets-tools/configs/rollup-plugin-widget-typing.js renamed to packages/pluggable-widgets-tools/configs/rollup-plugin-widget-typing.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { promises as fs } from "fs";
22
import { extname, join } from "path";
3-
import { listDir } from "./shared";
3+
import { listDir } from "./shared.mjs";
44

5-
const { transformPackage } = require("../dist/typings-generator");
5+
const { transformPackage } = await import(new URL("../dist/typings-generator/index.js", import.meta.url));
66

77
export function widgetTyping({ sourceDir }) {
88
let firstRun = true;

packages/pluggable-widgets-tools/configs/rollup.config.js renamed to packages/pluggable-widgets-tools/configs/rollup.config.mjs

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
/* eslint-disable @typescript-eslint/explicit-function-return-type */
22

3-
import { existsSync } from "fs";
4-
import { join } from "path";
3+
import { existsSync } from "node:fs";
4+
import { join } from "node:path";
5+
import { fileURLToPath } from "node:url";
56
import alias from "@rollup/plugin-alias";
67
import { getBabelInputPlugin, getBabelOutputPlugin } from "@rollup/plugin-babel";
78
import commonjs from "@rollup/plugin-commonjs";
89
import image from "@rollup/plugin-image";
910
import { nodeResolve } from "@rollup/plugin-node-resolve";
1011
import replace from "rollup-plugin-re";
1112
import typescript from "@rollup/plugin-typescript";
12-
import { blue } from "ansi-colors";
13+
import colors from "ansi-colors";
1314
import postcssImport from "postcss-import";
1415
import postcssUrl from "postcss-url";
15-
import loadConfigFile from "rollup/dist/loadConfigFile";
16+
import { loadConfigFile } from "rollup/dist/loadConfigFile.js";
1617
import clear from "rollup-plugin-clear";
1718
import command from "rollup-plugin-command";
1819
import license from "rollup-plugin-license";
1920
import livereload from "rollup-plugin-livereload";
2021
import postcss from "rollup-plugin-postcss";
21-
import { terser } from "rollup-plugin-terser";
22-
import { cp } from "shelljs";
23-
import { widgetTyping } from "./rollup-plugin-widget-typing";
22+
import terser from "@rollup/plugin-terser";
23+
import shelljs from "shelljs";
24+
import { widgetTyping } from "./rollup-plugin-widget-typing.mjs";
2425
import {
2526
editorConfigEntry,
2627
isTypescript,
@@ -32,9 +33,11 @@ import {
3233
widgetPackage,
3334
widgetVersion,
3435
onwarn
35-
} from "./shared";
36-
import { copyLicenseFile, createMpkFile, licenseCustomTemplate } from "./helpers/rollup-helper";
37-
import url from "./rollup-plugin-assets";
36+
} from "./shared.mjs";
37+
import { copyLicenseFile, createMpkFile, licenseCustomTemplate } from "./helpers/rollup-helper.mjs";
38+
import url from "./rollup-plugin-assets.mjs";
39+
40+
const { cp } = shelljs;
3841

3942
const outDir = join(sourcePath, "/dist/tmp/widgets/");
4043
const outWidgetDir = join(widgetPackage.replace(/\./g, "/"), widgetName.toLowerCase());
@@ -93,7 +96,7 @@ const cssUrlTransform = asset =>
9396
export default async args => {
9497
const production = Boolean(args.configProduction);
9598
if (!production && projectPath) {
96-
console.info(blue(`Project Path: ${projectPath}`));
99+
console.info(colors.blue(`Project Path: ${projectPath}`));
97100
}
98101

99102
const result = [];
@@ -118,7 +121,7 @@ export default async args => {
118121
postCssPlugin(outputFormat, production),
119122
alias({
120123
entries: {
121-
"react-hot-loader/root": join(__dirname, "hot")
124+
"react-hot-loader/root": fileURLToPath(new URL("hot", import.meta.url)),
122125
}
123126
}),
124127
...getCommonPlugins({
@@ -178,6 +181,7 @@ export default async args => {
178181
sourcemap: false
179182
},
180183
external: commonExternalLibs,
184+
strictDeprecations: true,
181185
treeshake: { moduleSideEffects: false },
182186
plugins: [
183187
url({ include: ["**/*.svg"], limit: 143360 }), // SVG file size limit of 140 kB
@@ -191,7 +195,7 @@ export default async args => {
191195
{
192196
closeBundle() {
193197
if (!process.env.ROLLUP_WATCH) {
194-
setTimeout(() => process.exit(0));
198+
setTimeout(() => process.exit(0));
195199
}
196200
},
197201
name: 'force-close'
@@ -201,9 +205,14 @@ export default async args => {
201205
});
202206
}
203207

204-
const customConfigPath = join(sourcePath, "rollup.config.js");
205-
if (existsSync(customConfigPath)) {
206-
const customConfig = await loadConfigFile(customConfigPath, { ...args, configDefaultConfig: result });
208+
const customConfigPathJS = join(sourcePath, "rollup.config.js");
209+
const customConfigPathESM = join(sourcePath, "rollup.config.mjs");
210+
const existingConfigPath =
211+
existsSync(customConfigPathJS) ? customConfigPathJS
212+
: existsSync(customConfigPathESM) ? customConfigPathESM
213+
: null;
214+
if (existingConfigPath != null) {
215+
const customConfig = await loadConfigFile(existingConfigPath, { ...args, configDefaultConfig: result });
207216
customConfig.warnings.flush();
208217
return customConfig.options;
209218
}
@@ -215,12 +224,12 @@ export default async args => {
215224
nodeResolve({ preferBuiltins: false, mainFields: ["module", "browser", "main"] }),
216225
isTypescript
217226
? typescript({
218-
noEmitOnError: !args.watch,
219-
sourceMap: config.sourceMaps,
220-
inlineSources: config.sourceMaps,
221-
target: "es2019", // we transpile the result with babel anyway, see below
222-
exclude: ["**/__tests__/**/*"]
223-
})
227+
noEmitOnError: !args.watch,
228+
sourceMap: config.sourceMaps,
229+
inlineSources: config.sourceMaps,
230+
target: "es2022", // we transpile the result with babel anyway, see below
231+
exclude: ["**/__tests__/**/*"]
232+
})
224233
: null,
225234
// Babel can transpile source JS and resulting JS, hence are input/output plugins. The good
226235
// practice is to do the most of conversions on resulting code, since then we ensure that
@@ -230,7 +239,6 @@ export default async args => {
230239
sourceMaps: config.sourceMaps,
231240
babelrc: false,
232241
babelHelpers: "bundled",
233-
plugins: ["@babel/plugin-proposal-class-properties"],
234242
overrides: [
235243
{
236244
test: /node_modules/,
@@ -258,29 +266,29 @@ export default async args => {
258266
}),
259267
config.transpile
260268
? getBabelOutputPlugin({
261-
sourceMaps: config.sourceMaps,
262-
babelrc: false,
263-
compact: false,
264-
...(config.babelConfig || {})
265-
})
269+
sourceMaps: config.sourceMaps,
270+
babelrc: false,
271+
compact: false,
272+
...(config.babelConfig || {})
273+
})
266274
: null,
267275
image(),
268276
production ? terser() : null,
269277
config.licenses
270278
? license({
271-
thirdParty: {
272-
includePrivate: true,
273-
output: [
274-
{
275-
file: join(outDir, "dependencies.txt")
276-
},
277-
{
278-
file: join(outDir, "dependencies.json"),
279-
template: licenseCustomTemplate
280-
}
281-
]
282-
}
283-
})
279+
thirdParty: {
280+
includePrivate: true,
281+
output: [
282+
{
283+
file: join(outDir, "dependencies.txt")
284+
},
285+
{
286+
file: join(outDir, "dependencies.json"),
287+
template: licenseCustomTemplate
288+
}
289+
]
290+
}
291+
})
284292
: null,
285293
// We need to create .mpk and copy results to test project after bundling is finished.
286294
// In case of a regular build is it is on `writeBundle` of the last config we define

0 commit comments

Comments
 (0)