Skip to content

Commit f91bfba

Browse files
committed
feat(compiler): add ReplexicaContentScope
1 parent 77252fd commit f91bfba

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import * as t from '@babel/types';
2+
import { NodePath } from '@babel/core';
3+
import { IReplexicaScope } from "./types";
4+
import { findImmediateJsxParent, getImportName, hasJsxTextChildren, injectImport } from './../../utils/ast';
5+
import { ReplexicaChunk } from './chunk';
6+
import { generateScopeId } from '../../utils/id';
7+
import { ReplexicaScopeData, ReplexicaScopeHint } from '../types';
8+
import { ReplexicaBaseScope } from './base';
9+
10+
export class ReplexicaContentScope extends ReplexicaBaseScope implements IReplexicaScope {
11+
public static fromNode(path: NodePath<t.Node>): IReplexicaScope[] {
12+
if (!path.isJSXElement() && !path.isJSXFragment()) { return []; }
13+
// to return true, must have non-empty when trimmed JSXText children
14+
// and either not have a parent JSX element at all, or have a parent JSX element with no text children
15+
const hasTextContent = hasJsxTextChildren(path);
16+
if (!hasTextContent) { return []; }
17+
18+
const jsxElementContainer = findImmediateJsxParent(path);
19+
if (jsxElementContainer && hasJsxTextChildren(jsxElementContainer)) { return []; }
20+
21+
const scope = new ReplexicaContentScope(path);
22+
return [scope];
23+
}
24+
25+
private constructor(
26+
private readonly path: NodePath<t.JSXElement | t.JSXFragment>,
27+
) {
28+
super();
29+
const _scope = this;
30+
31+
path.traverse({
32+
JSXOpeningElement(path) {
33+
path.skip();
34+
},
35+
JSXText(path) {
36+
const chunk = ReplexicaChunk.fromJsxText(path);
37+
if (chunk.text.length) {
38+
_scope._chunks.add(chunk);
39+
}
40+
},
41+
JSXExpressionContainer(path) {
42+
const chunk = ReplexicaChunk.fromJsxExpressionContainer(path);
43+
if (chunk.text.length) {
44+
_scope._chunks.add(chunk);
45+
}
46+
},
47+
});
48+
49+
const chunkIds = Array.from(this._chunks).map((chunk) => chunk.id);
50+
this._id = generateScopeId(chunkIds, 0);
51+
}
52+
53+
private _chunks: Set<ReplexicaChunk> = new Set();
54+
private _id: string;
55+
56+
public get id(): string {
57+
return this._id;
58+
}
59+
60+
public injectIntl(fileId: string, isServer: boolean, i18nImportPrefix: string): ReplexicaScopeData {
61+
const result: ReplexicaScopeData = {};
62+
63+
const programNode = this.path.findParent((p) => p.isProgram()) as NodePath<t.Program> | null;
64+
if (!programNode) { throw new Error(`Couldn't find file node`); }
65+
66+
const packageName = isServer ? '@replexica/react/server' : '@replexica/react/client';
67+
const localHelperName = isServer ? 'ReplexicaServerChunk' : 'ReplexicaClientChunk';
68+
69+
for (const chunk of this._chunks) {
70+
let helperName = getImportName(programNode, packageName, localHelperName);
71+
if (!helperName) {
72+
helperName = injectImport(programNode, packageName, localHelperName);
73+
}
74+
75+
const injectedElement = t.jsxOpeningElement(
76+
t.jsxIdentifier(helperName),
77+
[
78+
t.jsxAttribute(t.jsxIdentifier('fileId'), t.stringLiteral(fileId)),
79+
t.jsxAttribute(t.jsxIdentifier('scopeId'), t.stringLiteral(this.id)),
80+
t.jsxAttribute(t.jsxIdentifier('chunkId'), t.stringLiteral(chunk.id)),
81+
],
82+
true,
83+
);
84+
85+
if (isServer) {
86+
// add the following prop to the injected element:
87+
// importer={(locale) => import(`./${i18nImportPrefix}/${locale}.json`).then((m) => m.default)}
88+
const importer = t.arrowFunctionExpression(
89+
[t.identifier('locale')],
90+
t.callExpression(
91+
t.memberExpression(
92+
t.callExpression(t.identifier('import'), [
93+
t.templateLiteral([
94+
t.templateElement({ raw: `./${i18nImportPrefix}/`, cooked: `./${i18nImportPrefix}/` }, false),
95+
t.templateElement({ raw: '.json', cooked: '.json' }, true),
96+
], [t.identifier('locale')]),
97+
]),
98+
t.identifier('then'),
99+
),
100+
[
101+
t.arrowFunctionExpression(
102+
[t.identifier('m')],
103+
t.memberExpression(t.identifier('m'), t.identifier('default')),
104+
),
105+
],
106+
),
107+
);
108+
109+
injectedElement.attributes.push(
110+
t.jsxAttribute(
111+
t.jsxIdentifier('importer'),
112+
t.jsxExpressionContainer(importer),
113+
),
114+
);
115+
}
116+
117+
chunk.path.replaceWith(
118+
t.jsxElement(
119+
injectedElement,
120+
null,
121+
[],
122+
true,
123+
)
124+
);
125+
126+
result[chunk.id] = chunk.text;
127+
}
128+
129+
return result;
130+
}
131+
132+
public extractHints(): ReplexicaScopeHint[] {
133+
const result = this._extractBaseHints(this.path);
134+
return result;
135+
}
136+
}

0 commit comments

Comments
 (0)