Skip to content

Commit adc2e2a

Browse files
committed
github in javascript
1 parent d3362aa commit adc2e2a

File tree

4 files changed

+94
-83
lines changed

4 files changed

+94
-83
lines changed

codepress-swc-plugin/src/lib.rs

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ pub struct CodePressTransform {
262262
use_js_metadata_map: bool,
263263
metadata_map: HashMap<String, MetadataEntry>,
264264
inserted_metadata_map: bool,
265+
// Track if config (repo/branch) has been injected to window.__CODEPRESS_CONFIG__
266+
inserted_config: bool,
265267
}
266268

267269
/// Metadata entry for the JS-based map (window.__CODEPRESS_MAP__)
@@ -477,6 +479,7 @@ impl CodePressTransform {
477479
use_js_metadata_map,
478480
metadata_map: HashMap::new(),
479481
inserted_metadata_map: false,
482+
inserted_config: false,
480483
}
481484
}
482485

@@ -937,47 +940,9 @@ impl CodePressTransform {
937940
})
938941
}
939942

940-
fn create_repo_attr(&self) -> Option<JSXAttrOrSpread> {
941-
self.repo_name.as_ref().map(|repo| {
942-
JSXAttrOrSpread::JSXAttr(JSXAttr {
943-
span: DUMMY_SP,
944-
name: JSXAttrName::Ident(cp_ident_name("codepress-github-repo-name".into())),
945-
value: Some(make_jsx_str_attr_value(repo.clone())),
946-
})
947-
})
948-
}
949-
950-
fn create_branch_attr(&self) -> Option<JSXAttrOrSpread> {
951-
self.branch_name.as_ref().map(|branch| {
952-
JSXAttrOrSpread::JSXAttr(JSXAttr {
953-
span: DUMMY_SP,
954-
name: JSXAttrName::Ident(cp_ident_name("codepress-github-branch".into())),
955-
value: Some(make_jsx_str_attr_value(branch.clone())),
956-
})
957-
})
958-
}
959-
960-
fn has_repo_attribute(&self, attrs: &[JSXAttrOrSpread]) -> bool {
961-
attrs.iter().any(|attr| {
962-
if let JSXAttrOrSpread::JSXAttr(jsx_attr) = attr {
963-
if let JSXAttrName::Ident(ident) = &jsx_attr.name {
964-
return ident.sym.as_ref() == "codepress-github-repo-name";
965-
}
966-
}
967-
false
968-
})
969-
}
970-
971-
fn has_branch_attribute(&self, attrs: &[JSXAttrOrSpread]) -> bool {
972-
attrs.iter().any(|attr| {
973-
if let JSXAttrOrSpread::JSXAttr(jsx_attr) = attr {
974-
if let JSXAttrName::Ident(ident) = &jsx_attr.name {
975-
return ident.sym.as_ref() == "codepress-github-branch";
976-
}
977-
}
978-
false
979-
})
980-
}
943+
// NOTE: create_repo_attr, create_branch_attr, has_repo_attribute, has_branch_attribute
944+
// were removed - repo/branch info is now injected via window.__CODEPRESS_CONFIG__
945+
// in inject_config() instead of as HTML attributes.
981946

982947
// ---------- binding collection & tracing ----------
983948

@@ -2138,6 +2103,64 @@ impl CodePressTransform {
21382103
self.inserted_metadata_map = true;
21392104
}
21402105

2106+
/// Injects window.__CODEPRESS_CONFIG__ with repo and branch info.
2107+
/// This stores config in JS instead of as HTML attributes (which pollutes the DOM).
2108+
/// Only injected once per build (uses static flag to prevent duplicates across modules).
2109+
fn inject_config(&mut self, m: &mut Module) {
2110+
// Skip if already injected (either in this module or globally)
2111+
if self.inserted_config || GLOBAL_ATTRIBUTES_ADDED.load(Ordering::Relaxed) {
2112+
return;
2113+
}
2114+
2115+
// Only inject if we have repo info
2116+
let repo = match &self.repo_name {
2117+
Some(r) => r.clone(),
2118+
None => return,
2119+
};
2120+
2121+
let branch = self.branch_name.clone().unwrap_or_else(|| "main".to_string());
2122+
2123+
// Build the config object: window.__CODEPRESS_CONFIG__ = { repo: "...", branch: "..." }
2124+
// Uses Object.assign to avoid overwriting if somehow multiple modules try to set it
2125+
let js = format!(
2126+
"try{{if(typeof window!=='undefined'){{window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{{}},{{repo:\"{}\",branch:\"{}\"}});}}}}catch(_){{}}",
2127+
repo.replace('\\', "\\\\").replace('"', "\\\""),
2128+
branch.replace('\\', "\\\\").replace('"', "\\\"")
2129+
);
2130+
2131+
let stmt = ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2132+
span: DUMMY_SP,
2133+
expr: Box::new(Expr::Call(CallExpr {
2134+
span: DUMMY_SP,
2135+
callee: Callee::Expr(Box::new(Expr::New(NewExpr {
2136+
span: DUMMY_SP,
2137+
callee: Box::new(Expr::Ident(cp_ident("Function".into()))),
2138+
args: Some(vec![ExprOrSpread {
2139+
spread: None,
2140+
expr: Box::new(Expr::Lit(Lit::Str(Str {
2141+
span: DUMMY_SP,
2142+
value: js.into(),
2143+
raw: None,
2144+
}))),
2145+
}]),
2146+
type_args: None,
2147+
#[cfg(not(feature = "compat_0_87"))]
2148+
ctxt: SyntaxContext::empty(),
2149+
}))),
2150+
args: vec![],
2151+
type_args: None,
2152+
#[cfg(not(feature = "compat_0_87"))]
2153+
ctxt: SyntaxContext::empty(),
2154+
})),
2155+
}));
2156+
2157+
// Place AFTER directive prologue (e.g., "use client"; "use strict")
2158+
let insert_at = self.directive_insert_index(m);
2159+
m.body.insert(insert_at, stmt);
2160+
self.inserted_config = true;
2161+
GLOBAL_ATTRIBUTES_ADDED.store(true, Ordering::Relaxed);
2162+
}
2163+
21412164
/// Injects the CPRefreshProvider at app entry points (when use_js_metadata_map is true).
21422165
/// This enables automatic HMR without users needing to manually add the provider.
21432166
///
@@ -3240,6 +3263,8 @@ impl VisitMut for CodePressTransform {
32403263
self.inject_graph_stmt(m);
32413264
// Inject metadata map (if using JS-based metadata instead of DOM attributes)
32423265
self.inject_metadata_map(m);
3266+
// Inject config (repo/branch) into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes)
3267+
self.inject_config(m);
32433268
}
32443269
fn visit_mut_import_decl(&mut self, n: &mut ImportDecl) {
32453270
let _ = self.file_from_span(n.span);
@@ -3686,26 +3711,8 @@ impl VisitMut for CodePressTransform {
36863711
}
36873712
}
36883713

3689-
// Root repo/branch once
3690-
if self.repo_name.is_some() && !GLOBAL_ATTRIBUTES_ADDED.load(Ordering::Relaxed) {
3691-
let element_name = match &node.opening.name {
3692-
JSXElementName::Ident(ident) => ident.sym.as_ref(),
3693-
_ => "",
3694-
};
3695-
if matches!(element_name, "html" | "body" | "div") {
3696-
if !self.has_repo_attribute(&node.opening.attrs) {
3697-
if let Some(repo_attr) = self.create_repo_attr() {
3698-
node.opening.attrs.push(repo_attr);
3699-
}
3700-
}
3701-
if !self.has_branch_attribute(&node.opening.attrs) {
3702-
if let Some(branch_attr) = self.create_branch_attr() {
3703-
node.opening.attrs.push(branch_attr);
3704-
}
3705-
}
3706-
GLOBAL_ATTRIBUTES_ADDED.store(true, Ordering::Relaxed);
3707-
}
3708-
}
3714+
// NOTE: repo/branch info is now injected via window.__CODEPRESS_CONFIG__ in inject_config()
3715+
// instead of as HTML attributes, to avoid polluting the DOM.
37093716

37103717
// Host vs custom
37113718
let is_host = matches!(

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codepress/codepress-engine",
3-
"version": "0.8.0",
3+
"version": "0.9.0",
44
"packageManager": "pnpm@10.22.0",
55
"description": "CodePress engine - Babel and SWC plug-ins",
66
"main": "./dist/index.js",

src/esbuild-plugin.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ function xorEncodePath(input: string): string {
3535

3636
/**
3737
* Inject codepress-data-fp attributes into JSX opening tags
38+
* Note: repo/branch config is now injected via window.__CODEPRESS_CONFIG__ at module level
3839
*/
39-
function injectJSXAttributes(source: string, encoded: string, repoName?: string, branchName?: string): string {
40+
function injectJSXAttributes(source: string, encoded: string): string {
4041
const lines = source.split('\n');
4142
const output: string[] = [];
4243
let lineNum = 0;
@@ -60,20 +61,10 @@ function injectJSXAttributes(source: string, encoded: string, repoName?: string,
6061
const modifiedLine = line.replace(
6162
/(^|\s+|[\s{(>]|return\s+|=\s*|:\s*|\?\s*)<([A-Z][\w.]*|[a-z]+)([\s\/>])/g,
6263
(match, before, tagName, after) => {
63-
// Build attributes
64+
// Build attributes - only codepress-data-fp, repo/branch are injected via window.__CODEPRESS_CONFIG__
6465
const attrs: string[] = [];
6566
attrs.push(`codepress-data-fp="${encoded}:${lineNum}-${lineNum}"`);
6667

67-
// Add repo/branch info to container elements (divs, sections, etc.)
68-
if (/^[a-z]/.test(tagName)) {
69-
if (repoName) {
70-
attrs.push(`codepress-github-repo-name="${repoName}"`);
71-
}
72-
if (branchName) {
73-
attrs.push(`codepress-github-branch="${branchName}"`);
74-
}
75-
}
76-
7768
return `${before}<${tagName} ${attrs.join(' ')}${after}`;
7869
}
7970
);
@@ -112,10 +103,19 @@ export function createCodePressPlugin(options: CodePressPluginOptions = {}): Plu
112103

113104
// Inject JSX attributes (codepress-data-fp)
114105
// HMR is handled by a root-level CPRefreshProvider, not per-component wrapping
115-
const transformed = injectJSXAttributes(source, encoded, repo_name, branch_name);
106+
const transformed = injectJSXAttributes(source, encoded);
107+
108+
// Inject config (repo/branch) into window.__CODEPRESS_CONFIG__ at module level
109+
// This keeps the DOM clean instead of polluting HTML with attributes
110+
let configPrefix = '';
111+
if (repo_name) {
112+
const escapedRepo = repo_name.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
113+
const escapedBranch = (branch_name || 'main').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
114+
configPrefix = `try{if(typeof window!=='undefined'){window.__CODEPRESS_CONFIG__=Object.assign(window.__CODEPRESS_CONFIG__||{},{repo:"${escapedRepo}",branch:"${escapedBranch}"});}}catch(_){}\n`;
115+
}
116116

117117
return {
118-
contents: transformed,
118+
contents: configPrefix + transformed,
119119
loader: 'tsx',
120120
};
121121
} catch (err) {

src/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,19 @@ try {
284284
}
285285
}
286286

287-
// Inject repo/branch script (existing code)
287+
// Inject repo/branch config into window.__CODEPRESS_CONFIG__ (cleaner than DOM attributes)
288288
if (!globalAttributesAdded && repoName && state.file.encodedPath) {
289+
const escapedRepo = repoName.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
290+
const escapedBranch = (branch || 'main').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
289291
const scriptCode = `
290-
(function() {
291-
if (typeof document !== 'undefined' && document.body && !document.body.hasAttribute('${REPO_ATTRIBUTE_NAME}')) {
292-
document.body.setAttribute('${REPO_ATTRIBUTE_NAME}', '${repoName}');
293-
${branch ? `document.body.setAttribute('${BRANCH_ATTRIBUTE_NAME}', '${branch}');` : ''}
292+
try {
293+
if (typeof window !== 'undefined') {
294+
window.__CODEPRESS_CONFIG__ = Object.assign(window.__CODEPRESS_CONFIG__ || {}, {
295+
repo: "${escapedRepo}",
296+
branch: "${escapedBranch}"
297+
});
294298
}
295-
})();
299+
} catch (_) {}
296300
`;
297301

298302
const scriptAst = babel.template.ast(scriptCode);
@@ -304,7 +308,7 @@ try {
304308

305309
globalAttributesAdded = true;
306310
console.log(
307-
`\x1b[32m✓ Injected repo/branch + graph in ${path.basename(
311+
`\x1b[32m✓ Injected config + graph in ${path.basename(
308312
state.file.opts.filename ?? "unknown"
309313
)}\x1b[0m`
310314
);

0 commit comments

Comments
 (0)