This guide covers migrating a real CommonJS-based Fractal project (the typical shape: fractal.config.js using require()/module.exports, a custom Mandelbrot theme, custom Handlebars helpers/partials) to this fork. It was validated end-to-end against a real, large production styleguide (194 components, custom theme, custom helpers/partials from three separate packages) — the fork produced an identical component tree and byte-identical raw component output.
Replace every @frctl/* dependency with its @fractality/* equivalent:
| Before | After |
|---|---|
@frctl/fractal |
@fractality/fractality |
@frctl/handlebars |
@fractality/handlebars |
@frctl/mandelbrot |
@fractality/mandelbrot |
@frctl/nunjucks |
@fractality/nunjucks |
@frctl/twig |
@fractality/twig |
@frctl/react |
@fractality/react |
The CLI binary is also renamed: fractal build / fractal start become fractality build / fractality start.
Every @fractality/* package is ESM-only ("type": "module", no CJS build). Node's native require() can load an ESM-only package (require(esm) is supported in modern Node), but it does not unwrap a default export automatically the way a bundler's CJS/ESM interop would. Concretely:
// Before (CJS, @frctl/*)
const handlebars = require('@frctl/handlebars');
handlebars({ helpers: {...} }); // handlebars is directly callable
// After, if you keep using require() as-is (BREAKS):
const handlebars = require('@fractality/handlebars');
handlebars({ helpers: {...} }); // TypeError: handlebars is not a function
// require() actually returns { __esModule: true, default: <the real thing> }@fractality/fractality itself is the one exception — it has a named create export in addition to its default export, so const { create } = require('@fractality/fractality') still works. Every adapter/theme package (handlebars, mandelbrot, nunjucks, twig, react) is default-export-only and will break under plain require().
The supported path is converting your config to real ESM, not chasing .default on every require call. In practice this means:
- Rename
fractal.config.js→fractal.config.mjs(orfractality.config.mjs; the legacyfractal.config.js/fractal.jsfilenames still work but print a deprecation notice) and your theme/extension entry files similarly. - Replace
require/module.exportswithimport/export default. - Replace
__dirname/__filenamewith the standard ESM equivalent:import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url));
- For helper/partial modules that are themselves still CommonJS (common for older internal packages), keep loading them with
require()viacreateRequirerather than converting them too — that's often out of scope for the Fractal migration itself:import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const helpers = require('./my-legacy-helpers.js');
// fractal.config.js (before)
const { create } = require('@frctl/fractal');
const handlebars = require('@frctl/handlebars');
const path = require('node:path');
const fractal = create();
fractal.web.theme(require('./theme'));
fractal.components.engine(handlebars({ helpers: { ...require('./helpers') } }));
module.exports = fractal;// fractal.config.mjs (after)
import { create } from '@fractality/fractality';
import handlebars from '@fractality/handlebars';
import path from 'node:path';
import { createRequire } from 'node:module';
import theme from './theme/index.mjs';
const require = createRequire(import.meta.url);
const fractal = create();
fractal.web.theme(theme);
fractal.components.engine(handlebars({ helpers: { ...require('./helpers') } }));
export default fractal;Do the same conversion for any custom theme file (theme/index.js → theme/index.mjs), converting its require('@frctl/mandelbrot') to import mandelbrot from '@fractality/mandelbrot' and its module.exports = theme to export default theme.
Since @fractality/* isn't a byte-for-byte drop-in (step 2 above is a real, one-time cost), verify the migration by diffing build output rather than trusting it blindly:
- Build once with the project unmodified (
@frctl/fractal) to a reference directory. - Apply the changes in this guide, build again to a separate directory.
diff -rqthe two build outputs. Expect differences in: build timestamps, any component context that uses non-seeded random data (e.g. Faker without a fixed seed), and small theme-version-related markup/asset-hash differences if your@fractality/mandelbrotversion has diverged from your previous@frctl/mandelbrotversion. Anything beyond that is worth investigating as a real incompatibility.
If you want to test unreleased/local changes to this fork itself (rather than the published npm packages) against a real consuming project without publishing: pnpm pack each @fractality/* package you need into tarballs, then add a pnpm-workspace.yaml to the consuming project (if it doesn't already have one) with an overrides section pointing every @fractality/* package at its local tarball path. A plain file: dependency on the package's source directory is not enough — this fork's own packages reference each other via the workspace:* protocol internally, which only resolves inside an actual pnpm workspace.
cachebust=<version>query strings on theme assets will change if your@fractality/mandelbrotversion differs from whatever@frctl/mandelbrotversion you were pinned to.- Syntax-highlighted code blocks (context/JSON panels) may render with additional
hljs-punctuationspans depending on the installedhighlight.jsversion — a highlight.js grammar change, not a Fractal/Fractality difference.