multiclone 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/agents/section-builder.md +5 -3
- package/commands/multiclone.md +2 -2
- package/dist/contracts/tools/session.js +9 -8
- package/dist/contracts/tools/session.js.map +1 -1
- package/dist/project/assets.js +15 -13
- package/dist/project/assets.js.map +1 -1
- package/dist/project/convert/attrs.js +318 -0
- package/dist/project/convert/attrs.js.map +1 -0
- package/dist/project/convert/convert.js +304 -0
- package/dist/project/convert/convert.js.map +1 -0
- package/dist/project/convert/css.js +299 -0
- package/dist/project/convert/css.js.map +1 -0
- package/dist/project/convert/html.js +425 -0
- package/dist/project/convert/html.js.map +1 -0
- package/dist/project/convert/jsx.js +335 -0
- package/dist/project/convert/jsx.js.map +1 -0
- package/dist/project/convert/locate.js +180 -0
- package/dist/project/convert/locate.js.map +1 -0
- package/dist/project/convert/scripts.js +214 -0
- package/dist/project/convert/scripts.js.map +1 -0
- package/dist/project/convert/styles.js +228 -0
- package/dist/project/convert/styles.js.map +1 -0
- package/dist/project/convert/write.js +142 -0
- package/dist/project/convert/write.js.map +1 -0
- package/dist/project/fonts.js +131 -0
- package/dist/project/fonts.js.map +1 -0
- package/dist/project/libraries.js +28 -0
- package/dist/project/libraries.js.map +1 -0
- package/dist/project/method.js +1 -1
- package/dist/project/method.js.map +1 -1
- package/dist/project/scaffold.js +33 -6
- package/dist/project/scaffold.js.map +1 -1
- package/dist/session/proc.js +29 -1
- package/dist/session/proc.js.map +1 -1
- package/dist/session/session.js +47 -4
- package/dist/session/session.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/multiclone/SKILL.md +15 -5
- package/templates/react-vite/index.html +1 -1
- package/templates/react-vite/src/main.tsx +2 -2
- package/templates/react-vite/src/styles/index.css +0 -1
- package/dist/project/draft.js +0 -860
- package/dist/project/draft.js.map +0 -1
- package/templates/react-vite/src/styles/draft.css +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ensureDir } from '../../core/files.js';
|
|
4
|
+
import { attr, decodeEntities } from './html.js';
|
|
5
|
+
const TRACKING = /googletagmanager|google-analytics|googlesyndication|doubleclick|googleadservices|gtag\(|\bfbq\(|connect\.facebook|facebook\.net|hotjar|clarity\.ms|segment\.(?:com|io)|cdn\.segment|mixpanel|amplitude|posthog|plausible\.io|fullstory|heapanalytics|intercom|hs-scripts|hs-analytics|hsforms|hubspot|snap\.licdn|linkedin\.com\/(?:insight|px)|ads-twitter|static\.ads|analytics\.tiktok|tiktok\.com\/i18n\/pixel|redditstatic|reddit\.com\/pixel|bat\.bing|cookiebot|onetrust|cookielaw|osano|termly|iubenda|usercentrics|cookieyes|consentmanager|cookie-?script|vwo\.|optimizely|newrelic|nr-data|browser\.sentry|sentry-cdn|datadoghq|rudderstack|klaviyo|crisp\.chat|js\.driftt|zdassets|zendesk|quora\.com\/_\/ad|pinimg\.com\/ct|matomo|umami\.|_vercel\/insights|_vercel\/speed-insights|vercel-insights|cloudflareinsights|beacon\.min\.js|gstatic\.com\/recaptcha|recaptcha\/api|hcaptcha|leadfeeder|lfeeder|apollo\.io|clearbit|6sc\.co|zoominfo|g2crowd|capterra|trustpilot\.com\/bootstrap|chatwidget|tawk\.to|livechatinc|olark|qualified\.com|chilipiper|rb2b|warmly|vector\.co/i;
|
|
6
|
+
/** App runtimes that would try to own the page; their bundles are dropped, libraries kept. */
|
|
7
|
+
const FRAMEWORKS = new Set([
|
|
8
|
+
'nextjs',
|
|
9
|
+
'remix',
|
|
10
|
+
'nuxt',
|
|
11
|
+
'gatsby',
|
|
12
|
+
'react',
|
|
13
|
+
'vue',
|
|
14
|
+
'svelte',
|
|
15
|
+
'angular',
|
|
16
|
+
'framer',
|
|
17
|
+
'wix',
|
|
18
|
+
]);
|
|
19
|
+
const LIBRARY = /gsap|scrolltrigger|scrollsmoother|splittext|lenis|locomotive|lottie|bodymovin|swiper|splide|glide|flickity|slick|three(?:\.module)?(?:\.min)?\.js|\/three@|ogl|pixi|rive|spline|anime(?:\.min)?\.js|aos(?:\.min)?\.js|barba|swup|typed|splitting|jquery|webflow|finsweet|fs-|alpine|matter|p5|tsparticles|particles|motion(?:\.min)?\.js|popmotion|vanilla-tilt|tilt|plyr|vimeo|youtube|mux|hls/i;
|
|
20
|
+
const DATA_TYPES = /^(application\/(ld\+)?json|text\/(x-)?template|text\/html|text\/x-handlebars|importmap|speculationrules)$/i;
|
|
21
|
+
/** Decides which original scripts the clone runs and writes local copies into public/. */
|
|
22
|
+
export async function planScripts(input) {
|
|
23
|
+
const framework = input.stack.some((id) => FRAMEWORKS.has(id));
|
|
24
|
+
const pageOrigin = new URL(input.pageUrl).origin;
|
|
25
|
+
const bodies = new Map(input.network.filter((entry) => entry.file).map((entry) => [entry.url, entry]));
|
|
26
|
+
const scripts = [];
|
|
27
|
+
const dropped = [];
|
|
28
|
+
const outDir = join(input.project, 'public', ...input.publicDir.split('/'));
|
|
29
|
+
let index = 0;
|
|
30
|
+
for (const element of input.elements) {
|
|
31
|
+
const type = (attr(element, 'type') ?? '').trim().toLowerCase();
|
|
32
|
+
const rawSrc = attr(element, 'src');
|
|
33
|
+
const text = decodeEntities(element.children.map((child) => (child.kind === 'text' ? child.raw : '')).join(''));
|
|
34
|
+
const attrs = keptAttributes(element);
|
|
35
|
+
if (type && DATA_TYPES.test(type)) {
|
|
36
|
+
if (type === 'importmap' || type === 'speculationrules')
|
|
37
|
+
continue;
|
|
38
|
+
if (text.trim())
|
|
39
|
+
scripts.push({ text, type, ...(Object.keys(attrs).length ? { attrs } : {}) });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const module = type === 'module';
|
|
43
|
+
if (type && !module && !/javascript|ecmascript|^text\/babel$/.test(type)) {
|
|
44
|
+
dropped.push({ reason: `type ${type}`, what: rawSrc ?? 'inline script' });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let url = null;
|
|
48
|
+
if (rawSrc) {
|
|
49
|
+
try {
|
|
50
|
+
url = new URL(rawSrc, input.pageUrl);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
dropped.push({ reason: 'bad URL', what: rawSrc });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const what = url?.href ?? `inline script: ${text.trim().slice(0, 60)}`;
|
|
58
|
+
if (TRACKING.test(url?.href ?? '') || (!url && TRACKING.test(text.slice(0, 4000)))) {
|
|
59
|
+
dropped.push({ reason: 'tracking or consent', what });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (framework && !LIBRARY.test(url?.pathname ?? text.slice(0, 2000))) {
|
|
63
|
+
const sameOrigin = url ? url.origin === pageOrigin : true;
|
|
64
|
+
if (sameOrigin || module) {
|
|
65
|
+
dropped.push({ reason: 'framework app code (the React clone replaces it)', what });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
index++;
|
|
70
|
+
const order = String(index).padStart(2, '0');
|
|
71
|
+
const base = {
|
|
72
|
+
...(module ? { type: 'module' } : {}),
|
|
73
|
+
...(Object.keys(attrs).length ? { attrs } : {}),
|
|
74
|
+
};
|
|
75
|
+
if (!url) {
|
|
76
|
+
if (!text.trim())
|
|
77
|
+
continue;
|
|
78
|
+
const name = `${order}-inline.js`;
|
|
79
|
+
await ensureDir(outDir);
|
|
80
|
+
await writeFile(join(outDir, name), `${text.trim()}\n`);
|
|
81
|
+
scripts.push({ src: `/${input.publicDir}/${name}`, ...base });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const async = element.attrs.some(([key]) => key.toLowerCase() === 'async');
|
|
85
|
+
const entry = bodies.get(url.href);
|
|
86
|
+
const body = entry?.file ? await input.readBody(entry.file) : null;
|
|
87
|
+
// Modules import siblings by relative URL; a lone copy would break them, so they load from the original.
|
|
88
|
+
const portable = body !== null && !(module && /\bimport\s*[({"'\w*]|\bfrom\s*["']/.test(body));
|
|
89
|
+
if (portable) {
|
|
90
|
+
const name = `${order}-${fileName(url)}`;
|
|
91
|
+
await ensureDir(outDir);
|
|
92
|
+
await writeFile(join(outDir, name), body);
|
|
93
|
+
scripts.push({ src: `/${input.publicDir}/${name}`, ...base, ...(async ? { async } : {}) });
|
|
94
|
+
}
|
|
95
|
+
else if (/^(localhost|127\.|0\.0\.0\.0|\[::1\])/.test(url.hostname)) {
|
|
96
|
+
dropped.push({ reason: 'points at the original developer machine', what });
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
scripts.push({ src: url.href, ...base, ...(async ? { async } : {}) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { scripts, kept: scripts.length, dropped };
|
|
103
|
+
}
|
|
104
|
+
/** Attributes scripts read about themselves (data-* config, id); src/type/async are handled apart. */
|
|
105
|
+
function keptAttributes(element) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [rawName, value] of element.attrs) {
|
|
108
|
+
const name = rawName.toLowerCase();
|
|
109
|
+
if ([
|
|
110
|
+
'src',
|
|
111
|
+
'type',
|
|
112
|
+
'async',
|
|
113
|
+
'defer',
|
|
114
|
+
'integrity',
|
|
115
|
+
'nonce',
|
|
116
|
+
'crossorigin',
|
|
117
|
+
'referrerpolicy',
|
|
118
|
+
].includes(name))
|
|
119
|
+
continue;
|
|
120
|
+
if (/^[a-z_][\w\-:.]*$/.test(name))
|
|
121
|
+
out[name] = decodeEntities(value);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function fileName(url) {
|
|
126
|
+
const last = url.pathname.split('/').filter(Boolean).pop() ?? 'script';
|
|
127
|
+
const clean = last.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 60) || 'script';
|
|
128
|
+
return clean.endsWith('.js') || clean.endsWith('.mjs') ? clean : `${clean}.js`;
|
|
129
|
+
}
|
|
130
|
+
/** Browser helper written into the clone: applies <html>/<body> attributes and runs the scripts. */
|
|
131
|
+
export const RUNTIME_SOURCE = `/**
|
|
132
|
+
* Runs the original site's scripts on the converted page, in their original order, after React
|
|
133
|
+
* has rendered the markup they expect. Generated by multiclone; keep it while the page still uses
|
|
134
|
+
* the site's own JS. Replace a script with real React code section by section, then remove it.
|
|
135
|
+
*/
|
|
136
|
+
export interface OriginalScript {
|
|
137
|
+
readonly src?: string;
|
|
138
|
+
readonly text?: string;
|
|
139
|
+
readonly type?: string;
|
|
140
|
+
readonly async?: boolean;
|
|
141
|
+
readonly attrs?: Readonly<Record<string, string>>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface OriginalPage {
|
|
145
|
+
/** Attributes of the original <html> and <body> (Webflow ids, theme classes). */
|
|
146
|
+
readonly html: Readonly<Record<string, string>>;
|
|
147
|
+
readonly body: Readonly<Record<string, string>>;
|
|
148
|
+
readonly scripts: readonly OriginalScript[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type Marked = Window & { __multicloneRan?: Set<string> };
|
|
152
|
+
|
|
153
|
+
function applyAttributes(target: HTMLElement, attrs: Readonly<Record<string, string>>): void {
|
|
154
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
155
|
+
if (name === 'class') target.classList.add(...value.split(/\\s+/).filter(Boolean));
|
|
156
|
+
else target.setAttribute(name, value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Inline handlers (onclick=...) were kept as data-mc-on* attributes; put them back. */
|
|
161
|
+
function restoreHandlers(): void {
|
|
162
|
+
for (const element of Array.from(document.body.querySelectorAll('*'))) {
|
|
163
|
+
for (const { name, value } of Array.from(element.attributes)) {
|
|
164
|
+
if (name.startsWith('data-mc-on')) element.setAttribute(name.slice(8), value);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function load(script: OriginalScript): Promise<void> {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
const element = document.createElement('script');
|
|
172
|
+
for (const [name, value] of Object.entries(script.attrs ?? {})) element.setAttribute(name, value);
|
|
173
|
+
if (script.type) element.type = script.type;
|
|
174
|
+
if (script.text !== undefined) {
|
|
175
|
+
element.text = script.text;
|
|
176
|
+
document.body.appendChild(element);
|
|
177
|
+
resolve();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!script.src) {
|
|
181
|
+
resolve();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
element.async = script.async === true;
|
|
185
|
+
element.src = script.src;
|
|
186
|
+
element.addEventListener('load', () => {
|
|
187
|
+
resolve();
|
|
188
|
+
});
|
|
189
|
+
element.addEventListener('error', () => {
|
|
190
|
+
console.warn('[multiclone] original script failed to load:', script.src);
|
|
191
|
+
resolve();
|
|
192
|
+
});
|
|
193
|
+
document.body.appendChild(element);
|
|
194
|
+
if (script.async === true) resolve();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Runs once per page load (StrictMode re-runs effects; scripts must not run twice). */
|
|
199
|
+
export async function runOriginalPage(key: string, page: OriginalPage): Promise<void> {
|
|
200
|
+
const marked = window as Marked;
|
|
201
|
+
const ran = (marked.__multicloneRan ??= new Set<string>());
|
|
202
|
+
if (ran.has(key)) return;
|
|
203
|
+
ran.add(key);
|
|
204
|
+
applyAttributes(document.documentElement, page.html);
|
|
205
|
+
applyAttributes(document.body, page.body);
|
|
206
|
+
restoreHandlers();
|
|
207
|
+
for (const script of page.scripts) await load(script);
|
|
208
|
+
// Code that waits for these events missed them while the bundle loaded; replay them once.
|
|
209
|
+
document.dispatchEvent(new Event('DOMContentLoaded', { bubbles: true }));
|
|
210
|
+
window.dispatchEvent(new Event('load'));
|
|
211
|
+
window.dispatchEvent(new Event('resize'));
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
//# sourceMappingURL=scripts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../../src/project/convert/scripts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { parseCss, printCss, rewriteCssUrls, ruleOwner, selectorTokens, SHARED, splitSelectors, } from './css.js';
|
|
2
|
+
import { attr, decodeEntities } from './html.js';
|
|
3
|
+
/** Collects the page's real CSS, points its URLs at local copies, and splits it by section. */
|
|
4
|
+
export async function buildStyles(input) {
|
|
5
|
+
const bodies = new Map(input.network.filter((entry) => entry.file).map((entry) => [entry.url, entry]));
|
|
6
|
+
const remote = [];
|
|
7
|
+
const imports = [];
|
|
8
|
+
let sheets = 0;
|
|
9
|
+
const rewrite = (css, base) => rewriteCssUrls(css, (raw) => {
|
|
10
|
+
let absolute;
|
|
11
|
+
try {
|
|
12
|
+
absolute = new URL(raw, base).href;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return input.local(absolute) ?? absolute;
|
|
18
|
+
});
|
|
19
|
+
const load = async (url, depth) => {
|
|
20
|
+
const entry = bodies.get(url);
|
|
21
|
+
const body = entry?.file ? await input.readBody(entry.file) : null;
|
|
22
|
+
if (body === null)
|
|
23
|
+
return null;
|
|
24
|
+
return inlineImports(body, url, depth);
|
|
25
|
+
};
|
|
26
|
+
const inlineImports = async (css, base, depth) => {
|
|
27
|
+
let out = '';
|
|
28
|
+
let last = 0;
|
|
29
|
+
for (const match of css.matchAll(/@import\s+(?:url\(\s*)?(["']?)([^"')\s;]+)\1\s*\)?([^;]*);/gi)) {
|
|
30
|
+
out += css.slice(last, match.index);
|
|
31
|
+
last = match.index + match[0].length;
|
|
32
|
+
let url;
|
|
33
|
+
try {
|
|
34
|
+
url = new URL(match[2] ?? '', base).href;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const media = (match[3] ?? '').trim();
|
|
40
|
+
const inner = depth < 4 ? await load(url, depth + 1) : null;
|
|
41
|
+
if (inner === null) {
|
|
42
|
+
imports.push(`@import url('${url}')${media ? ` ${media}` : ''};`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
out += media ? `@media ${media} {\n${inner}\n}` : inner;
|
|
46
|
+
}
|
|
47
|
+
return rewrite(out + css.slice(last), base);
|
|
48
|
+
};
|
|
49
|
+
const chunks = [];
|
|
50
|
+
for (const element of input.elements) {
|
|
51
|
+
if (element.tag === 'style') {
|
|
52
|
+
const text = decodeEntities(element.children.map((child) => (child.kind === 'text' ? child.raw : '')).join(''));
|
|
53
|
+
const media = attr(element, 'media');
|
|
54
|
+
const css = await inlineImports(text, input.pageUrl, 0);
|
|
55
|
+
chunks.push(wrapMedia(css, media));
|
|
56
|
+
sheets++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const rel = (attr(element, 'rel') ?? '').toLowerCase().split(/\s+/);
|
|
60
|
+
const href = attr(element, 'href');
|
|
61
|
+
const asStyle = rel.includes('preload') && (attr(element, 'as') ?? '') === 'style';
|
|
62
|
+
if ((!rel.includes('stylesheet') && !asStyle) || rel.includes('alternate') || !href)
|
|
63
|
+
continue;
|
|
64
|
+
let url;
|
|
65
|
+
try {
|
|
66
|
+
url = new URL(href, input.pageUrl).href;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const css = await load(url, 0);
|
|
72
|
+
if (css === null) {
|
|
73
|
+
remote.push(url);
|
|
74
|
+
imports.push(`@import url('${url}');`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// media="print" + onload swap is the async-CSS trick: the sheet applies to every medium.
|
|
78
|
+
const media = attr(element, 'media');
|
|
79
|
+
const lazy = media === 'print' && (attr(element, 'onload') ?? '').includes('media');
|
|
80
|
+
chunks.push(wrapMedia(css, lazy ? null : media));
|
|
81
|
+
sheets++;
|
|
82
|
+
}
|
|
83
|
+
const rules = throughMountPoint(parseCss(chunks.join('\n\n')));
|
|
84
|
+
const decide = ownership(rules, input.elementsTokens);
|
|
85
|
+
const shared = [];
|
|
86
|
+
const sections = new Map();
|
|
87
|
+
let moved = 0;
|
|
88
|
+
let kept = 0;
|
|
89
|
+
const place = (rule, owner, wrap) => {
|
|
90
|
+
if (owner === SHARED) {
|
|
91
|
+
shared.push(wrap(rule));
|
|
92
|
+
kept++;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
moved++;
|
|
96
|
+
const list = sections.get(owner) ?? [];
|
|
97
|
+
sections.set(owner, list);
|
|
98
|
+
list.push(wrap(rule));
|
|
99
|
+
};
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
if (rule.children && /^@(media|supports|container)\b/i.test(rule.prelude)) {
|
|
102
|
+
for (const child of rule.children) {
|
|
103
|
+
const owner = child.children ? SHARED : (decide.get(child) ?? SHARED);
|
|
104
|
+
place(child, owner, (inner) => ({ ...rule, children: [inner] }));
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
place(rule, rule.children ? SHARED : (decide.get(rule) ?? SHARED), (inner) => inner);
|
|
109
|
+
}
|
|
110
|
+
const header = imports.length ? `${[...new Set(imports)].join('\n')}\n\n` : '';
|
|
111
|
+
return {
|
|
112
|
+
base: `${header}${printCss(mergeAdjacent(shared))}\n`,
|
|
113
|
+
sections: new Map([...sections].map(([owner, list]) => [owner, `${printCss(mergeAdjacent(list))}\n`])),
|
|
114
|
+
sheets,
|
|
115
|
+
remote,
|
|
116
|
+
moved,
|
|
117
|
+
kept,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function wrapMedia(css, media) {
|
|
121
|
+
const query = media?.trim();
|
|
122
|
+
return query && query.toLowerCase() !== 'all' ? `@media ${query} {\n${css}\n}` : css;
|
|
123
|
+
}
|
|
124
|
+
/** Re-joins consecutive copies of the same @media block that splitting broke apart. */
|
|
125
|
+
function mergeAdjacent(rules) {
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const rule of rules) {
|
|
128
|
+
const prev = out[out.length - 1];
|
|
129
|
+
if (prev?.children && rule.children && prev.prelude === rule.prelude) {
|
|
130
|
+
out[out.length - 1] = { ...prev, children: [...prev.children, ...rule.children] };
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
out.push(rule);
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Section owner of every style rule (top level and inside @media/@supports). A rule moves into
|
|
139
|
+
* its section only when no shared rule after it could hit the same elements: section sheets load
|
|
140
|
+
* after the shared one, so moving such a rule would flip which declaration wins.
|
|
141
|
+
*/
|
|
142
|
+
function ownership(rules, elements) {
|
|
143
|
+
const owners = new Map();
|
|
144
|
+
const byToken = new Map();
|
|
145
|
+
for (const element of elements) {
|
|
146
|
+
for (const token of element.tokens) {
|
|
147
|
+
const set = owners.get(token) ?? new Set();
|
|
148
|
+
owners.set(token, set);
|
|
149
|
+
set.add(element.owner);
|
|
150
|
+
const list = byToken.get(token) ?? [];
|
|
151
|
+
byToken.set(token, list);
|
|
152
|
+
list.push(element);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const flat = [];
|
|
156
|
+
for (const rule of rules) {
|
|
157
|
+
if (rule.children && /^@(media|supports|container)\b/i.test(rule.prelude)) {
|
|
158
|
+
for (const child of rule.children)
|
|
159
|
+
if (!child.children)
|
|
160
|
+
flat.push(child);
|
|
161
|
+
}
|
|
162
|
+
else if (!rule.children && !rule.prelude.startsWith('@'))
|
|
163
|
+
flat.push(rule);
|
|
164
|
+
}
|
|
165
|
+
const decided = new Map();
|
|
166
|
+
const laterTokens = new Set();
|
|
167
|
+
const laterTags = new Set();
|
|
168
|
+
for (let i = flat.length - 1; i >= 0; i--) {
|
|
169
|
+
const rule = flat[i];
|
|
170
|
+
if (!rule || rule.prelude.startsWith('@'))
|
|
171
|
+
continue;
|
|
172
|
+
let owner = ruleOwner(rule.prelude, owners);
|
|
173
|
+
const selectors = splitSelectors(rule.prelude);
|
|
174
|
+
const tokens = selectors.flatMap(selectorTokens);
|
|
175
|
+
if (owner !== SHARED) {
|
|
176
|
+
for (const token of tokens) {
|
|
177
|
+
for (const element of byToken.get(token) ?? []) {
|
|
178
|
+
if (laterTags.has('*') ||
|
|
179
|
+
laterTags.has(element.tag) ||
|
|
180
|
+
element.tokens.some((t) => laterTokens.has(t)))
|
|
181
|
+
owner = SHARED;
|
|
182
|
+
if (owner === SHARED)
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
if (owner === SHARED)
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
decided.set(rule, owner);
|
|
190
|
+
if (owner === SHARED) {
|
|
191
|
+
for (const token of tokens)
|
|
192
|
+
laterTokens.add(token);
|
|
193
|
+
for (const selector of selectors) {
|
|
194
|
+
if (selectorTokens(selector).length)
|
|
195
|
+
continue;
|
|
196
|
+
const subject = subjectTag(selector);
|
|
197
|
+
if (subject && subject !== 'html' && subject !== 'body' && subject !== ':root')
|
|
198
|
+
laterTags.add(subject);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return decided;
|
|
203
|
+
}
|
|
204
|
+
/** Tag of a selector's last compound (`nav a:hover` → `a`); `*` when it has none. */
|
|
205
|
+
function subjectTag(selector) {
|
|
206
|
+
const stripped = selector
|
|
207
|
+
.replace(/:[\w-]+\((?:[^()]|\([^()]*\))*\)/g, '')
|
|
208
|
+
.replace(/\[[^\]]*\]/g, '');
|
|
209
|
+
const last = stripped
|
|
210
|
+
.split(/\s*[>+~]\s*|\s+/)
|
|
211
|
+
.filter(Boolean)
|
|
212
|
+
.pop() ?? '';
|
|
213
|
+
if (last.startsWith(':root'))
|
|
214
|
+
return ':root';
|
|
215
|
+
const tag = /^([a-zA-Z][\w-]*|\*)/.exec(last)?.[1];
|
|
216
|
+
return tag ? tag.toLowerCase() : '*';
|
|
217
|
+
}
|
|
218
|
+
/** `body > .page` must still match now that the page renders inside the clone's #mc-root. */
|
|
219
|
+
function throughMountPoint(rules) {
|
|
220
|
+
return rules.map((rule) => {
|
|
221
|
+
if (rule.children)
|
|
222
|
+
return { ...rule, children: throughMountPoint(rule.children) };
|
|
223
|
+
if (rule.prelude.startsWith('@') || !/\bbody\s*>/.test(rule.prelude))
|
|
224
|
+
return rule;
|
|
225
|
+
return { ...rule, prelude: rule.prelude.replace(/\bbody\s*>\s*/g, 'body > #mc-root > ') };
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../src/project/convert/styles.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { format } from 'prettier';
|
|
4
|
+
import { ensureDir, readJson, writeJson } from '../../core/files.js';
|
|
5
|
+
/** Prettier with the clone's settings; unformattable input is written as is. */
|
|
6
|
+
export async function pretty(source, parser) {
|
|
7
|
+
try {
|
|
8
|
+
return await format(source, {
|
|
9
|
+
parser,
|
|
10
|
+
singleQuote: true,
|
|
11
|
+
trailingComma: 'all',
|
|
12
|
+
printWidth: 100,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return source;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function sectionSource(options) {
|
|
20
|
+
const imports = [
|
|
21
|
+
options.cssProperties ? "import type { CSSProperties } from 'react';" : '',
|
|
22
|
+
options.css ? `import './${options.name}.css';` : '',
|
|
23
|
+
].filter(Boolean);
|
|
24
|
+
return `// Converted by multiclone from ${options.origin} (section "${options.label.replace(/\n/g, ' ')}").
|
|
25
|
+
// Original markup and class names; its styles are in ${options.css ? `./${options.name}.css and ` : ''}src/styles/original. Polish it, don't redraw it.
|
|
26
|
+
${imports.join('\n')}
|
|
27
|
+
|
|
28
|
+
export function ${options.name}() {
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
${options.jsx}
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
export function pageSource(options) {
|
|
38
|
+
const sections = options.sections
|
|
39
|
+
.map((name) => `import { ${name} } from '@/sections/${options.slug}/${name}/${name}';`)
|
|
40
|
+
.join('\n');
|
|
41
|
+
return `// Page converted by multiclone from ${options.origin}. Sections live in src/sections/${options.slug};
|
|
42
|
+
// the original site's scripts run from src/original/${options.slug}.ts until they are rewritten in React.
|
|
43
|
+
import '@/styles/original/${options.slug}/base.css';
|
|
44
|
+
import { useEffect${options.cssProperties ? ', type CSSProperties' : ''} } from 'react';
|
|
45
|
+
import { runOriginalPage } from '@/lib/original';
|
|
46
|
+
import { original } from '@/original/${options.slug}';
|
|
47
|
+
${sections}
|
|
48
|
+
|
|
49
|
+
export function ${options.name}() {
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
void runOriginalPage(${JSON.stringify(options.route)}, original);
|
|
52
|
+
}, []);
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
${options.jsx}
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
export function originalSource(page) {
|
|
62
|
+
return `// The original page's <html>/<body> attributes and scripts, in document order. Generated by multiclone.
|
|
63
|
+
import type { OriginalPage } from '@/lib/original';
|
|
64
|
+
|
|
65
|
+
export const original: OriginalPage = ${JSON.stringify(page, null, 2)};
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
/** Declares tags React's types do not know (custom elements like dotlottie-player). */
|
|
69
|
+
export async function writeElementTypes(project, tags) {
|
|
70
|
+
const file = join(project, 'src', 'original', 'elements.d.ts');
|
|
71
|
+
const existing = await readFile(file, 'utf8').catch(() => '');
|
|
72
|
+
const all = new Set(tags);
|
|
73
|
+
for (const match of existing.matchAll(/^\s+'([^']+)': Loose;$/gm))
|
|
74
|
+
if (match[1])
|
|
75
|
+
all.add(match[1]);
|
|
76
|
+
if (!all.size)
|
|
77
|
+
return;
|
|
78
|
+
const lines = [...all].sort().map((tag) => ` '${tag}': Loose;`);
|
|
79
|
+
await ensureDir(join(project, 'src', 'original'));
|
|
80
|
+
await writeFile(file, `// Tags from the original page that React's types do not list. Generated by multiclone.
|
|
81
|
+
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
82
|
+
|
|
83
|
+
type Loose = DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & Record<string, unknown>;
|
|
84
|
+
|
|
85
|
+
declare module 'react' {
|
|
86
|
+
namespace JSX {
|
|
87
|
+
interface IntrinsicElements {
|
|
88
|
+
${lines.join('\n')}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`);
|
|
93
|
+
}
|
|
94
|
+
/** Keeps the list of converted pages and regenerates src/app/routes.ts (one lazy chunk per page). */
|
|
95
|
+
export async function registerPages(project, entries) {
|
|
96
|
+
const file = join(project, '.multiclone', 'pages.json');
|
|
97
|
+
const taken = new Set(entries.map((entry) => entry.route));
|
|
98
|
+
const pages = (await readJson(file).catch(() => [])).filter((page) => !taken.has(page.route));
|
|
99
|
+
pages.push(...entries);
|
|
100
|
+
pages.sort((a, b) => a.route.localeCompare(b.route));
|
|
101
|
+
await writeJson(file, pages);
|
|
102
|
+
const components = new Map(pages.map((page) => [page.component, page.file]));
|
|
103
|
+
const lazies = [...components]
|
|
104
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
105
|
+
.map(([component, path]) => `const ${component} = lazy(() => import('${path}').then((module) => ({ default: module.${component} })));`)
|
|
106
|
+
.join('\n');
|
|
107
|
+
const routes = pages
|
|
108
|
+
.map((page) => ` ${JSON.stringify(page.route)}: ${page.component},`)
|
|
109
|
+
.join('\n');
|
|
110
|
+
await writeFile(join(project, 'src', 'app', 'routes.ts'), await pretty(`// Generated by multiclone: one entry per converted page. Each page loads as its own chunk,
|
|
111
|
+
// so one page's original CSS never leaks into another.
|
|
112
|
+
import { lazy, type ComponentType } from 'react';
|
|
113
|
+
|
|
114
|
+
${lazies}
|
|
115
|
+
|
|
116
|
+
export const routes: Partial<Record<string, ComponentType>> = {
|
|
117
|
+
${routes}
|
|
118
|
+
};
|
|
119
|
+
`, 'typescript'));
|
|
120
|
+
}
|
|
121
|
+
/** App shell for converted pages: no extra smooth-scroll layer, the site's own scripts own scrolling. */
|
|
122
|
+
export async function writeApp(project) {
|
|
123
|
+
await writeFile(join(project, 'src', 'app', 'App.tsx'), await pretty(`import { Suspense } from 'react';
|
|
124
|
+
import { Page } from '@/app/Page';
|
|
125
|
+
import { routes } from '@/app/routes';
|
|
126
|
+
|
|
127
|
+
// Links are plain anchors (full page loads), so the route is read once per load.
|
|
128
|
+
const path = window.location.pathname.replace(/\\/+$/, '') || '/';
|
|
129
|
+
const Current = routes[path] ?? Page;
|
|
130
|
+
|
|
131
|
+
export function App() {
|
|
132
|
+
return (
|
|
133
|
+
<Suspense fallback={null}>
|
|
134
|
+
<Current />
|
|
135
|
+
</Suspense>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
`, 'typescript'));
|
|
139
|
+
// The template reset and the old drawn draft would fight the original CSS; keep fonts and tokens.
|
|
140
|
+
await writeFile(join(project, 'src', 'styles', 'index.css'), "@import './fonts.css';\n@import './tokens.css';\n");
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../../src/project/convert/write.ts"],"names":[],"mappings":"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"}
|