Skip to content

feat: migrate to native ESM with no build step#4411

Merged
matthew-dean merged 11 commits intoless:masterfrom
matthew-dean:esm-migration
Mar 9, 2026
Merged

feat: migrate to native ESM with no build step#4411
matthew-dean merged 11 commits intoless:masterfrom
matthew-dean:esm-migration

Conversation

@matthew-dean
Copy link
Copy Markdown
Member

@matthew-dean matthew-dean commented Mar 9, 2026

Summary

  • Rename src/ to lib/ — source files are shipped directly with no compilation step
  • Native ESM: Added "type": "module" to package.json, targeting Node 18+
  • No build for Node: The src/lib/ transpilation step is removed entirely
  • Browser build still works: Rollup UMD bundle reads from lib/ directly. Bundle size actually decreased (509KB → 500KB unminified, 158KB → 153KB minified)
  • CJS files renamed to .cjs: Gruntfile.cjs, .eslintrc.cjs, @plugin test fixtures, and browser generator configs
  • Stop tracking dist/: Generated browser bundles removed from source control (served via npm/CDN)
  • CI fixes: fail-fast: false, playwright moved to root devDependencies, pnpm exec for playwright install
  • All 139 tests pass with no regressions

Key changes

  • .js extensions added to all relative imports for ESM resolution
  • createRequire() used for npm package resolution and JSON imports (optional deps like clean-css, source-map)
  • bin/lessc, test files, and build scripts converted from CJS to ESM
  • CI matrix updated with fail-fast: false so all jobs run independently

Test plan

  • All 139 Node.js tests pass (npm test)
  • Browser build generates successfully (node build/rollup.js --dist)
  • CLI works (bin/lessc compiles .less files)
  • Browser test generator works (ESM conversion)
  • CI passes on ubuntu/macos/windows with Node current, lts/*, lts/-1, lts/-2, lts/-3

- Rename src/ to lib/ — source files are shipped directly, no compilation
- Add "type": "module" to package.json for native ESM support (Node 18+)
- Convert bin/lessc, test files, and build scripts from CJS to ESM
- Rename Gruntfile.js and .eslintrc.js to .cjs (must remain CommonJS)
- Add .js extensions to all relative import paths for ESM resolution
- Use createRequire() for optional dependency resolution (npm packages, JSON)
- Configure TypeScript for check-only mode (noEmit: true, allowJs: true)
- Update Rollup config to read from lib/ directly
- Update CI matrix to drop Node 16 (minimum Node 18+)
- Browser build is smaller: 500KB (was 509KB), minified 153KB (was 158KB)
- All 139 tests pass
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

Important

Review skipped

Too many files!

This PR contains 157 files, which is 7 over the limit of 150.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 24263514-ac85-4adc-93e2-663b51ed9527

📥 Commits

Reviewing files that changed from the base of the PR and between 6f82d19 and f227c11.

⛔ Files ignored due to path filters (7)
  • dist/less.js is excluded by !**/dist/**
  • dist/less.min.js is excluded by !**/dist/**, !**/*.min.js
  • dist/less.min.js.map is excluded by !**/dist/**, !**/*.map, !**/*.min.js.map
  • packages/less/dist/less.js is excluded by !**/dist/**
  • packages/less/dist/less.min.js is excluded by !**/dist/**, !**/*.min.js
  • packages/less/dist/less.min.js.map is excluded by !**/dist/**, !**/*.map, !**/*.min.js.map
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (157)
  • .coderabbit.yaml
  • .github/workflows/ci.yml
  • .gitignore
  • package.json
  • packages/less/.eslintrc.cjs
  • packages/less/.gitignore
  • packages/less/Gruntfile.cjs
  • packages/less/bin/lessc
  • packages/less/build/banner.js
  • packages/less/build/rollup.js
  • packages/less/lib/less-browser/add-default-options.js
  • packages/less/lib/less-browser/bootstrap.js
  • packages/less/lib/less-browser/browser.js
  • packages/less/lib/less-browser/cache.js
  • packages/less/lib/less-browser/error-reporting.js
  • packages/less/lib/less-browser/file-manager.js
  • packages/less/lib/less-browser/image-size.js
  • packages/less/lib/less-browser/index.js
  • packages/less/lib/less-browser/log-listener.js
  • packages/less/lib/less-browser/plugin-loader.js
  • packages/less/lib/less-browser/utils.js
  • packages/less/lib/less-node/environment.js
  • packages/less/lib/less-node/file-manager.js
  • packages/less/lib/less-node/fs.js
  • packages/less/lib/less-node/image-size.js
  • packages/less/lib/less-node/index.js
  • packages/less/lib/less-node/lessc-helper.js
  • packages/less/lib/less-node/plugin-loader.js
  • packages/less/lib/less-node/url-file-manager.js
  • packages/less/lib/less/constants.js
  • packages/less/lib/less/contexts.js
  • packages/less/lib/less/data/colors.js
  • packages/less/lib/less/data/index.js
  • packages/less/lib/less/data/unit-conversions.js
  • packages/less/lib/less/default-options.js
  • packages/less/lib/less/deprecation.js
  • packages/less/lib/less/environment/abstract-file-manager.js
  • packages/less/lib/less/environment/abstract-plugin-loader.js
  • packages/less/lib/less/environment/environment-api.ts
  • packages/less/lib/less/environment/environment.js
  • packages/less/lib/less/environment/file-manager-api.ts
  • packages/less/lib/less/functions/boolean.js
  • packages/less/lib/less/functions/color-blending.js
  • packages/less/lib/less/functions/color.js
  • packages/less/lib/less/functions/data-uri.js
  • packages/less/lib/less/functions/default.js
  • packages/less/lib/less/functions/function-caller.js
  • packages/less/lib/less/functions/function-registry.js
  • packages/less/lib/less/functions/index.js
  • packages/less/lib/less/functions/list.js
  • packages/less/lib/less/functions/math-helper.js
  • packages/less/lib/less/functions/math.js
  • packages/less/lib/less/functions/number.js
  • packages/less/lib/less/functions/string.js
  • packages/less/lib/less/functions/style.js
  • packages/less/lib/less/functions/svg.js
  • packages/less/lib/less/functions/types.js
  • packages/less/lib/less/import-manager.js
  • packages/less/lib/less/index.js
  • packages/less/lib/less/less-error.js
  • packages/less/lib/less/logger.js
  • packages/less/lib/less/parse-tree.js
  • packages/less/lib/less/parse.js
  • packages/less/lib/less/parser/parser-input.js
  • packages/less/lib/less/parser/parser.js
  • packages/less/lib/less/plugin-manager.js
  • packages/less/lib/less/render.js
  • packages/less/lib/less/source-map-builder.js
  • packages/less/lib/less/source-map-output.js
  • packages/less/lib/less/transform-tree.js
  • packages/less/lib/less/tree/anonymous.js
  • packages/less/lib/less/tree/assignment.js
  • packages/less/lib/less/tree/atrule-syntax.js
  • packages/less/lib/less/tree/atrule.js
  • packages/less/lib/less/tree/attribute.js
  • packages/less/lib/less/tree/call.js
  • packages/less/lib/less/tree/color.js
  • packages/less/lib/less/tree/combinator.js
  • packages/less/lib/less/tree/comment.js
  • packages/less/lib/less/tree/condition.js
  • packages/less/lib/less/tree/container.js
  • packages/less/lib/less/tree/debug-info.js
  • packages/less/lib/less/tree/declaration.js
  • packages/less/lib/less/tree/detached-ruleset.js
  • packages/less/lib/less/tree/dimension.js
  • packages/less/lib/less/tree/element.js
  • packages/less/lib/less/tree/expression.js
  • packages/less/lib/less/tree/extend.js
  • packages/less/lib/less/tree/import.js
  • packages/less/lib/less/tree/index.js
  • packages/less/lib/less/tree/javascript.js
  • packages/less/lib/less/tree/js-eval-node.js
  • packages/less/lib/less/tree/keyword.js
  • packages/less/lib/less/tree/media.js
  • packages/less/lib/less/tree/merge-rules.js
  • packages/less/lib/less/tree/mixin-call.js
  • packages/less/lib/less/tree/mixin-definition.js
  • packages/less/lib/less/tree/namespace-value.js
  • packages/less/lib/less/tree/negative.js
  • packages/less/lib/less/tree/nested-at-rule.js
  • packages/less/lib/less/tree/node.js
  • packages/less/lib/less/tree/operation.js
  • packages/less/lib/less/tree/paren.js
  • packages/less/lib/less/tree/property.js
  • packages/less/lib/less/tree/query-in-parens.js
  • packages/less/lib/less/tree/quoted.js
  • packages/less/lib/less/tree/ruleset.js
  • packages/less/lib/less/tree/selector.js
  • packages/less/lib/less/tree/unicode-descriptor.js
  • packages/less/lib/less/tree/unit.js
  • packages/less/lib/less/tree/url.js
  • packages/less/lib/less/tree/value.js
  • packages/less/lib/less/tree/variable-call.js
  • packages/less/lib/less/tree/variable.js
  • packages/less/lib/less/utils.js
  • packages/less/lib/less/visitors/extend-visitor.js
  • packages/less/lib/less/visitors/import-sequencer.js
  • packages/less/lib/less/visitors/import-visitor.js
  • packages/less/lib/less/visitors/index.js
  • packages/less/lib/less/visitors/join-selector-visitor.js
  • packages/less/lib/less/visitors/set-tree-visibility-visitor.js
  • packages/less/lib/less/visitors/to-css-visitor.js
  • packages/less/lib/less/visitors/visitor.js
  • packages/less/package.json
  • packages/less/scripts/coverage-lines.js
  • packages/less/scripts/coverage-report.js
  • packages/less/scripts/postinstall.js
  • packages/less/src/less-node/environment.js
  • packages/less/src/less-node/fs.js
  • packages/less/src/less-node/index.js
  • packages/less/src/less/data/index.js
  • packages/less/src/less/tree/index.js
  • packages/less/src/less/visitors/index.js
  • packages/less/test/browser/generator/benchmark.config.cjs
  • packages/less/test/browser/generator/generate.cjs
  • packages/less/test/browser/generator/generate.js
  • packages/less/test/browser/generator/runner.cjs
  • packages/less/test/browser/generator/runner.config.cjs
  • packages/less/test/browser/generator/runner.js
  • packages/less/test/browser/generator/template.cjs
  • packages/less/test/browser/generator/utils.cjs
  • packages/less/test/index.js
  • packages/less/test/less-test.js
  • packages/less/test/mocha-playwright/runner.js
  • packages/less/test/modify-vars.js
  • packages/less/test/plugins/filemanager/index.cjs
  • packages/less/test/plugins/postprocess/index.cjs
  • packages/less/test/plugins/preprocess/index.cjs
  • packages/less/test/plugins/visitor/index.cjs
  • packages/less/test/sourcemaps/comprehensive.json
  • packages/less/test/test-es6.js
  • packages/less/tsconfig.build.json
  • packages/less/tsconfig.json
  • packages/test-data/tests-config/filemanagerPlugin/styles.config.cjs
  • packages/test-data/tests-config/postProcessorPlugin/styles.config.cjs
  • packages/test-data/tests-config/preProcessorPlugin/styles.config.cjs
  • packages/test-data/tests-config/visitorPlugin/styles.config.cjs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Generated browser bundles don't need to be in source control — they're
built during publish and included in the npm package via the files field.
Removes duplicate copies from both root dist/ and packages/less/dist/.
npx doesn't reliably find binaries with pnpm. Since playwright is
already a devDependency, use pnpm exec to run the installed version.
pnpm exec at workspace root can't find playwright binary since it's a
devDependency of the less package. Use --filter to run in that context.
Also disable fail-fast so all matrix jobs complete independently.
Makes pnpm exec playwright work from workspace root in CI.
copy-anything v2 lacks "type": "module", causing named import failures
on Node 18. v3 has proper ESM exports.

Revert testFolder to absolute path (matching original behavior) so debug
test path replacements match Less compiler output on Windows.
…ed package

Restricts npm package to only bin/, lib/, dist/, index.js, and README.md.
Previously shipped test files, Gruntfile, eslint config, etc.
Removes postinstall script (Playwright browser install) which only applies
in the monorepo dev environment and fails when installed from npm.

Verified: npm pack --dry-run shows 120 files (was 229), lessc CLI and
API both work from a clean tarball install.
@matthew-dean matthew-dean merged commit c21e465 into less:master Mar 9, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant