Skip to content

Commit c56b070

Browse files
authored
Merge pull request #103 from maxatwork/codex/legacy-migration-guide
docs: add legacy migration guide
2 parents 0f9928d + 2559d7f commit c56b070

10 files changed

Lines changed: 249 additions & 4 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Originally created in 2010, now rewritten for modern JavaScript, TypeScript, ESM
66

77
Legacy version is available in the [legacy branch](https://github.com/maxatwork/form2js/tree/legacy).
88

9+
Migrating from legacy form2js? Start with the [migration guide](https://maxatwork.github.io/form2js/migrate/).
10+
911
## Description
1012

1113
A small family of packages for turning form-shaped data into objects, and objects back into forms.
@@ -15,8 +17,23 @@ It is not a serializer, not an ORM, and not a new religion. It just does this on
1517
## Documentation
1618

1719
- [Docs Site](https://maxatwork.github.io/form2js/) - overview, installation, unified playground, and published API reference.
20+
- [Migration Guide](https://maxatwork.github.io/form2js/migrate/) - map old `form2js` and `jquery.toObject` usage to the current package family.
1821
- [API Reference Source](docs/api-index.md) - markdown source for the published API docs page.
1922

23+
## Migration from Legacy
24+
25+
If you are moving from the archived single-package version, start with the [migration guide](https://maxatwork.github.io/form2js/migrate/).
26+
27+
Quick package map:
28+
29+
- Legacy browser `form2js(...)` usage -> `@form2js/dom`
30+
- Legacy jQuery `$("#form").toObject()` usage -> `@form2js/jquery`
31+
- Server or pipeline `FormData` parsing -> `@form2js/form-data`
32+
- React submit handling -> `@form2js/react`
33+
- Object back into fields -> `@form2js/js2form`
34+
35+
The current project keeps the naming rules and core parsing model, but splits the old browser-era API into environment-specific packages.
36+
2037
## Packages
2138

2239
| Package | npm | Purpose | Module | Standalone | Node.js |

apps/docs/src/layouts/DocsShell.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
import "../styles/global.css";
3-
import { homepagePath } from "../lib/site-routes";
3+
import { homepagePath, migrationGuidePath } from "../lib/site-routes";
44
55
interface Props {
66
title?: string;
@@ -26,6 +26,7 @@ const basePath = import.meta.env.BASE_URL;
2626
</a>
2727
<nav class="site-nav" aria-label="Primary">
2828
<a href={homepagePath(basePath)}>Overview</a>
29+
<a href={migrationGuidePath(basePath)}>Migration</a>
2930
<a href="https://github.com/maxatwork/form2js">Github</a>
3031
</nav>
3132
</header>

apps/docs/src/lib/api-docs-source.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import {
99
apiIndexMarkdownPath,
1010
getApiPackageByMarkdownBasename
1111
} from "./api-packages";
12-
import { apiDocsPath, apiPackageDocsPath, homepagePath } from "./site-routes";
12+
import {
13+
apiDocsPath,
14+
apiPackageDocsPath,
15+
homepagePath,
16+
migrationGuidePath
17+
} from "./site-routes";
1318

1419
export interface ApiHeading {
1520
depth: 2 | 3;
@@ -77,6 +82,10 @@ function rewriteMarkdownLink(url: string, basePath: string): string {
7782
return `${homepagePath(basePath)}${suffix}`;
7883
}
7984

85+
if (normalizedPathname === "migrate.md") {
86+
return `${migrationGuidePath(basePath)}${suffix}`;
87+
}
88+
8089
if (normalizedPathname === "api.md" || normalizedPathname === "api-index.md") {
8190
return `${apiDocsPath(basePath)}${suffix}`;
8291
}

apps/docs/src/lib/site-routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export function homepagePath(basePath: string): string {
1111
return normalizeBase(basePath);
1212
}
1313

14+
export function migrationGuidePath(basePath: string): string {
15+
return `${normalizeBase(basePath)}migrate/`;
16+
}
17+
1418
export function apiDocsPath(basePath: string): string {
1519
return `${normalizeBase(basePath)}api/`;
1620
}

apps/docs/src/pages/migrate.astro

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
import path from "node:path";
3+
4+
import { ApiToc } from "../components/api/ApiToc";
5+
import { loadApiDocsSource } from "../lib/api-docs-source";
6+
import DocsShell from "../layouts/DocsShell.astro";
7+
8+
const migrationSource = await loadApiDocsSource({
9+
basePath: import.meta.env.BASE_URL,
10+
markdownPath: path.resolve(process.cwd(), "..", "..", "docs", "migrate.md")
11+
});
12+
---
13+
14+
<DocsShell title={`${migrationSource.title} | form2js`}>
15+
<section class="api-docs api-docs--content-sidebar">
16+
<article class="api-docs__content">
17+
<header class="api-docs__header">
18+
<p class="hero__eyebrow">Migration Guide</p>
19+
<h1>{migrationSource.title}</h1>
20+
{migrationSource.introHtml ? (
21+
<div class="api-docs__intro" set:html={migrationSource.introHtml} />
22+
) : null}
23+
</header>
24+
<div class="api-docs__body" set:html={migrationSource.bodyHtml} />
25+
</article>
26+
{migrationSource.headings.length > 0 ? (
27+
<aside class="api-docs__sidebar">
28+
<ApiToc client:load headings={migrationSource.headings} />
29+
</aside>
30+
) : null}
31+
</section>
32+
</DocsShell>

apps/docs/src/styles/global.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ a {
116116
gap: 2rem;
117117
}
118118

119+
.api-docs--content-sidebar {
120+
grid-template-columns: minmax(0, 1fr) 18rem;
121+
}
122+
119123
.api-docs__nav,
120124
.api-docs__sidebar {
121125
position: sticky;

apps/docs/test/api-docs-source.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const apiFormDataMarkdown = readFileSync(path.resolve(testDir, "../../../docs/ap
1414
const apiJqueryMarkdown = readFileSync(path.resolve(testDir, "../../../docs/api-jquery.md"), "utf8");
1515
const apiJs2formMarkdown = readFileSync(path.resolve(testDir, "../../../docs/api-js2form.md"), "utf8");
1616
const apiReactMarkdown = readFileSync(path.resolve(testDir, "../../../docs/api-react.md"), "utf8");
17+
const migrationMarkdown = readFileSync(path.resolve(testDir, "../../../docs/migrate.md"), "utf8");
1718
const readmeMarkdown = readFileSync(path.resolve(testDir, "../../../README.md"), "utf8");
1819

1920
describe("parseApiDocsMarkdown", () => {
@@ -115,4 +116,23 @@ Text.
115116
expect(apiReactMarkdown).toContain("npm install @form2js/react react");
116117
expect(readmeMarkdown).toContain("[API Reference Source](docs/api-index.md)");
117118
});
119+
120+
it("parses the migration guide markdown and rewrites package links", () => {
121+
const source = parseApiDocsMarkdown(migrationMarkdown, {
122+
basePath: "/form2js/"
123+
});
124+
125+
expect(source.title).toBe("Migrate from Legacy form2js");
126+
expect(source.introMarkdown).toContain("single `form2js` script");
127+
expect(source.bodyHtml).toContain('href="/form2js/api/dom/"');
128+
expect(source.bodyHtml).toContain('href="/form2js/api/jquery/"');
129+
expect(source.bodyHtml).toContain('href="/form2js/api/form-data/"');
130+
expect(source.headings).toEqual(
131+
expect.arrayContaining([
132+
expect.objectContaining({ slug: "quick-chooser", text: "Quick Chooser" }),
133+
expect.objectContaining({ slug: "legacy-api-mapping", text: "Legacy API Mapping" }),
134+
expect.objectContaining({ slug: "where-to-go-now", text: "Where To Go Now" })
135+
])
136+
);
137+
});
118138
});

apps/docs/test/homepage-shell.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const installSectionSource = readFileSync(
1010
path.resolve(testDir, "../src/components/landing/InstallSection.astro"),
1111
"utf8"
1212
);
13+
const docsShellSource = readFileSync(path.resolve(testDir, "../src/layouts/DocsShell.astro"), "utf8");
14+
const readmeSource = readFileSync(path.resolve(testDir, "../../../README.md"), "utf8");
1315

1416
describe("docs homepage shell", () => {
1517
it("wires the landing page sections together", () => {
@@ -27,4 +29,12 @@ describe("docs homepage shell", () => {
2729
expect(installSectionSource).toContain("https://unpkg.com/@form2js/dom/dist/standalone.global.js");
2830
expect(installSectionSource).toContain("https://unpkg.com/@form2js/jquery/dist/standalone.global.js");
2931
});
32+
33+
it("surfaces the migration guide in the shared docs chrome and the README", () => {
34+
expect(docsShellSource).toContain("migrationGuidePath");
35+
expect(docsShellSource).toContain(">Migration<");
36+
expect(readmeSource).toContain("Migrating from legacy form2js?");
37+
expect(readmeSource).toContain("https://maxatwork.github.io/form2js/migrate/");
38+
expect(readmeSource).toContain("## Migration from Legacy");
39+
});
3040
});

apps/docs/test/site-routes.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import {
44
apiDocsPath,
55
apiPackageDocsPath,
66
homepagePath,
7-
homepageVariantPath
7+
homepageVariantPath,
8+
migrationGuidePath
89
} from "../src/lib/site-routes";
910

1011
describe("site routes", () => {
11-
it("builds homepage and api paths under a base path", () => {
12+
it("builds homepage, migration, and api paths under a base path", () => {
1213
expect(homepagePath("/form2js/")).toBe("/form2js/");
14+
expect(migrationGuidePath("/form2js/")).toBe("/form2js/migrate/");
15+
expect(migrationGuidePath("/")).toBe("/migrate/");
1316
expect(apiDocsPath("/form2js/")).toBe("/form2js/api/");
1417
expect(apiPackageDocsPath("/form2js/", "react")).toBe("/form2js/api/react/");
1518
expect(apiPackageDocsPath("/", "form-data")).toBe("/api/form-data/");

docs/migrate.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Migrate from Legacy form2js
2+
3+
If you built around the old single `form2js` script or the archived jQuery plugin flow, the main change is that modern form2js is now a small package family. You install only the part you need instead of pulling one browser-era bundle into every environment.
4+
5+
The legacy code and historical examples still live in the [legacy branch](https://github.com/maxatwork/form2js/tree/legacy), but new work should move to the current packages and docs.
6+
7+
## Quick Chooser
8+
9+
| If your legacy code does this | Use now | Notes |
10+
| --- | --- | --- |
11+
| `form2js(form)` in the browser | [`@form2js/dom`](api-dom.md) | Closest direct replacement. Exports both `formToObject()` and a compatibility `form2js()` wrapper. |
12+
| `$("#form").toObject()` in jQuery | [`@form2js/jquery`](api-jquery.md) | Keeps the plugin shape while using the modern DOM parser underneath. |
13+
| Parse `FormData` on the server or in browser pipelines | [`@form2js/form-data`](api-form-data.md) | Best fit for fetch actions, loaders, workers, and Node. |
14+
| Handle submit state in React | [`@form2js/react`](api-react.md) | Wraps form parsing in a hook with async submit state and optional schema validation. |
15+
| Push object data back into a form | [`@form2js/js2form`](api-js2form.md) | Modern replacement for the old "object back into fields" helpers around the ecosystem. |
16+
| Work directly with path/value entries | [`@form2js/core`](api-core.md) | Lowest-level parser and formatter. |
17+
18+
## What Changed
19+
20+
- The archived project exposed one browser-oriented `form2js(rootNode, delimiter, skipEmpty, nodeCallback, useIdIfEmptyName)` entry point.
21+
- The current project splits that behavior by environment and responsibility.
22+
- Browser DOM extraction lives in `@form2js/dom`.
23+
- jQuery compatibility lives in `@form2js/jquery`.
24+
- `FormData`, React, object-to-form, and low-level entry parsing each have their own package.
25+
26+
That split is the point of the rewrite: smaller installs, clearer environment boundaries, and first-class TypeScript/ESM support without making every user drag along legacy browser assumptions.
27+
28+
## Legacy API Mapping
29+
30+
Legacy browser code usually looked like this:
31+
32+
```js
33+
var data = form2js(rootNode, ".", true, nodeCallback, false);
34+
```
35+
36+
Modern browser code should usually look like this:
37+
38+
```ts
39+
import { formToObject } from "@form2js/dom";
40+
41+
const data = formToObject(rootNode, {
42+
delimiter: ".",
43+
skipEmpty: true,
44+
nodeCallback,
45+
useIdIfEmptyName: false
46+
});
47+
```
48+
49+
If you want the smallest possible migration diff, `@form2js/dom` also exports a compatibility wrapper:
50+
51+
```ts
52+
import { form2js } from "@form2js/dom";
53+
54+
const data = form2js(rootNode, ".", true, nodeCallback, false);
55+
```
56+
57+
Parameter mapping:
58+
59+
| Legacy parameter | Modern equivalent |
60+
| --- | --- |
61+
| `rootNode` | `rootNode` |
62+
| `delimiter` | `options.delimiter` |
63+
| `skipEmpty` | `options.skipEmpty` |
64+
| `nodeCallback` | `options.nodeCallback` |
65+
| `useIdIfEmptyName` | `options.useIdIfEmptyName` |
66+
67+
The main migration decision is not the parameter mapping. It is choosing the right package for the environment where parsing now happens.
68+
69+
## Browser Migration
70+
71+
For plain browser forms, install `@form2js/dom`:
72+
73+
```bash
74+
npm install @form2js/dom
75+
```
76+
77+
Module usage:
78+
79+
```ts
80+
import { formToObject } from "@form2js/dom";
81+
82+
const data = formToObject(document.getElementById("profileForm"));
83+
```
84+
85+
Standalone usage is still available for the DOM package:
86+
87+
```html
88+
<script src="https://unpkg.com/@form2js/dom/dist/standalone.global.js"></script>
89+
<script>
90+
const data = formToObject(document.getElementById("profileForm"));
91+
// or form2js(document.getElementById("profileForm"))
92+
</script>
93+
```
94+
95+
## jQuery Migration
96+
97+
If your codebase still expects `$.fn.toObject()`, move to `@form2js/jquery` instead of rebuilding that glue yourself.
98+
99+
```bash
100+
npm install @form2js/jquery jquery
101+
```
102+
103+
```ts
104+
import $ from "jquery";
105+
import { installToObjectPlugin } from "@form2js/jquery";
106+
107+
installToObjectPlugin($);
108+
109+
const data = $("#profileForm").toObject({ mode: "first" });
110+
```
111+
112+
Standalone usage is also available:
113+
114+
```html
115+
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
116+
<script src="https://unpkg.com/@form2js/jquery/dist/standalone.global.js"></script>
117+
<script>
118+
const data = $("#profileForm").toObject({ mode: "combine" });
119+
</script>
120+
```
121+
122+
## Behavior Differences To Check
123+
124+
- `skipEmpty` still defaults to `true`, so empty strings and `null` values are skipped unless you opt out.
125+
- Disabled controls are ignored by default. Set `getDisabled: true` only if you really want them parsed.
126+
- Unsafe path segments such as `__proto__`, `prototype`, and `constructor` are rejected by default in the modern parser.
127+
- Only `@form2js/dom` and `@form2js/jquery` ship standalone browser globals. The other packages are module-only.
128+
- React and `FormData` use cases now have dedicated packages instead of being squeezed through the DOM entry point.
129+
130+
## Where To Go Now
131+
132+
If the legacy code used browser DOM access only because that was the only option at the time, this is the modern package map:
133+
134+
- Use [`@form2js/form-data`](api-form-data.md) when your app already has `FormData`, request entries, or server-side action handlers.
135+
- Use [`@form2js/react`](api-react.md) when you want submit-state handling around parsing in React.
136+
- Use [`@form2js/js2form`](api-js2form.md) when you need to populate forms from nested objects.
137+
- Use [`@form2js/core`](api-core.md) when you already have raw key/value pairs and just need the parser rules.
138+
139+
## Migration Checklist
140+
141+
1. Identify whether the old code is DOM-based, jQuery-based, React-based, or really just `FormData` processing.
142+
2. Swap the legacy package or script include for the specific current package.
143+
3. Move old positional arguments to an options object where appropriate.
144+
4. Re-test any custom `nodeCallback` logic and any flows that depend on disabled or empty fields.
145+
5. Replace browser-only parsing with `@form2js/form-data` or `@form2js/react` when the parsing no longer needs direct DOM traversal.

0 commit comments

Comments
 (0)