Skip to content

Commit

Permalink
Add <fbt:list>.
Browse files Browse the repository at this point in the history
  • Loading branch information
cpojer committed Dec 23, 2024
1 parent 8a26072 commit 6c0e39e
Show file tree
Hide file tree
Showing 65 changed files with 1,052 additions and 681 deletions.
9 changes: 7 additions & 2 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ const { existsSync, readFileSync } = require('node:fs');

module.exports = {
extends: ['@nkzw'],
ignorePatterns: ['packages/*/lib'],
ignorePatterns: ['packages/*/lib', 'packages/fbtee/lib-tmp/'],
overrides: [
{
files: ['**/__tests__/**/*.tsx'],
files: [
'./packages/babel-plugin-fbtee/src/bin/*.tsx',
'./packages/fbtee/babel-build.config.js',
'**/__tests__/**/*.tsx',
],
rules: {
'no-console': 0,
'workspaces/no-relative-imports': 0,
Expand All @@ -29,6 +33,7 @@ module.exports = {
devDependencies: [
'./example/vite.config.ts',
'./jest-preprocessor.js',
'./packages/fbtee/babel-build.config.js',
'**/__tests__/**/*.tsx',
],
packageDir: [__dirname].concat(
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ node_modules
packages/*/lib
packages/*/LICENSE
packages/*/README.md
packages/fbt/lib
packages/fbt/LICENSE
packages/fbtee/.enum_manifest.json
packages/fbtee/.src_manifest.json
packages/fbtee/lib-tmp
packages/fbtee/Strings.json
tsconfig.tsbuildinfo
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ example/source_strings.json
example/src/translatedFbts
example/src/translatedFbts.json
packages/*/lib/
packages/fbtee/lib-tmp/
pnpm-lock.yaml
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ The open-source version of `fbt`, however, became unmaintained, difficult to set
- **Easier Setup:** fbtee works with modern tools like Vite.
- **Statically Typed:** The fbtee compiler ensures correct usage of fbtee, libary TypeScript types are provided, and an eslint plugin helps fix common mistakes.
- **Improved React Compatibility:** Removed React-specific hacks and added support for implicit React fragments (`<>`).
- **Enhanced Features:** Fixed and exported `intlList`, which was not functional in the original `fbt`.
- **Enhanced Features:** Fixed and exported `inltList` as a new `<fbt:list>` construt, which was not functional in the original `fbt`.
- **Modernized Codebase:** Rewritten using TypeScript, ES modules (ESM), eslint, and modern JavaScript standards. Removed cruft and legacy code.
- **Updated Tooling:** Uses modern tools like pnpm, Vite, and esbuild for faster and more efficient development of **fbtee**.

Expand Down
24 changes: 22 additions & 2 deletions example/src/example/Example.react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ export default function Example() {
</span>
</span>
</fieldset>

<fieldset>
<span className="sentence example_row">
<fbt desc="example 1">
Expand All @@ -202,7 +201,6 @@ export default function Example() {
</fbt>
</span>
</fieldset>

<fieldset>
<span className={classNames('example_row', 'example_row--multi')}>
<span
Expand Down Expand Up @@ -287,6 +285,28 @@ export default function Example() {
</fbt>
</span>
</fieldset>
<fieldset>
<label>
<fbt desc="List example.">
Do you want to share a{' '}
<fbt:list
conjunction="or"
items={[
<fbt desc="Item in a list." key="photo">
photo
</fbt>,
<fbt desc="Item in a list." key="photo">
link
</fbt>,
<fbt desc="Item in a list." key="video">
video
</fbt>,
]}
name="list"
/>?
</fbt>
</label>
</fieldset>
<fieldset>
<span className="example_row">
<button
Expand Down
19 changes: 19 additions & 0 deletions example/src/example/__tests__/FbteeStrings-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { test } from '@jest/globals';
import { CollectFbtOutput } from '../../../../packages/babel-plugin-fbtee/src/bin/collect.tsx';

test('fbtee strings are included in the collected strings of the example project', () => {
const { phrases } = JSON.parse(
readFileSync(
join(import.meta.dirname, '../../../.source_strings.json'),
'utf8',
),
) as CollectFbtOutput;

expect(
phrases.some(
({ filepath }) => filepath === 'node_modules/fbtee/lib/index.js',
),
).toBe(true);
});
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ exports[`Example.react renders the example 1`] = `
their link.
</span>
</fieldset>
<fieldset>
<label>
Do you want to share a photo, link or video?
</label>
</fieldset>
<fieldset>
<span
class="example_row"
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"type": "module",
"scripts": {
"build": "pnpm -r build",
"build:all": "pnpm -r build && pnpm install && pnpm copy-files && cd example && pnpm build:fbtee",
"clean": "rm -rf packages/*/lib; cd example pnpm clean",
"copy-files": "find packages/* -type d -maxdepth 0 -exec cp README.md LICENSE {} \\;",
"build:all": "pnpm -r build && pnpm install && pnpm --filter=fbtee build:fbtee-strings && pnpm copy-files && cd example && pnpm build:fbtee",
"clean": "rm -rf packages/*/lib packages/fbtee/lib-tmp; cd example pnpm clean",
"copy-files": "find packages/* -type d -maxdepth 0 -exec cp README.md LICENSE Strings.json {} \\;",
"dev": "cd example && pnpm build:fbtee && pnpm dev",
"format": "prettier --write .",
"jest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --no-warnings\" node_modules/.bin/jest",
Expand All @@ -19,10 +19,12 @@
"tsc:check": "tsc"
},
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/generator": "^7.26.3",
"@babel/parser": "^7.26.3",
"@babel/plugin-syntax-import-attributes": "^7.26.0",
"@babel/plugin-syntax-typescript": "^7.25.9",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@babel/types": "^7.26.3",
Expand Down
5 changes: 1 addition & 4 deletions packages/babel-plugin-fbtee/src/FbtConstants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,7 @@ export const FbtBooleanOptions = {
} as const;

export const CommonOption = 'common';
export const FbtCallMustHaveAtLeastOneOfTheseAttributes = new Set([
'desc',
CommonOption,
]);
export const RequiredFbtAttributes = new Set(['desc', CommonOption]);

export const FbtRequiredAttributes = {
desc: true,
Expand Down
4 changes: 0 additions & 4 deletions packages/babel-plugin-fbtee/src/FbtNodeChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,6 @@ export default class FbtNodeChecker {
: null;
}

/**
* This is same as the non-static getFbtConstructNameFromFunctionCall except
* it accepts any of the three fbt modules (`FBT`, `FBS`).
*/
static getFbtNodeTypeFromFunctionCall(node: Node): FbtNodeType | null {
return (
(isCallExpression(node) &&
Expand Down
23 changes: 13 additions & 10 deletions packages/babel-plugin-fbtee/src/FbtUtil.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
JSXElement,
JSXExpressionContainer,
JSXFragment,
JSXOpeningElement,
JSXSpreadAttribute,
JSXText,
memberExpression,
Expand Down Expand Up @@ -62,8 +61,6 @@ import nullthrows from './nullthrows.tsx';

const { hasOwnProperty } = Object.prototype;

type JSXAttributes = ReadonlyArray<JSXOpeningElement['attributes'][number]>;

export type CallExpressionArg =
| Expression
| SpreadElement
Expand Down Expand Up @@ -130,6 +127,10 @@ export function checkOption<K extends string>(
): K {
const optionName = option as K;

if (optionName === 'key') {
return optionName;
}

const validValues = validOptions[optionName];
if (!hasOwnProperty.call(validOptions, optionName) || validValues == null) {
throw errorAt(
Expand Down Expand Up @@ -506,27 +507,29 @@ const isJSXAttributeWithValue = (
): node is JSXAttributeWithValue => node.value != null;

export function getAttributeByNameOrThrow(
attributes: JSXAttributes,
node: JSXElement,
name: string,
node: Node | null = null,
): JSXAttributeWithValue {
const attribute = getAttributeByName(attributes, name);
const attribute = getAttributeByName(node, name);
if (attribute == null) {
throw errorAt(node, `Unable to find attribute "${name}".`);
throw errorAt(node, `This node requires a '${name}' attribute.`);
}

if (!isJSXAttributeWithValue(attribute)) {
throw errorAt(node, `Attribute "${name}" has no value.`);
throw errorAt(
node,
`This '${name}' attribute of this node requires a value.`,
);
}

return attribute;
}

export function getAttributeByName(
attributes: JSXAttributes,
node: JSXElement,
name: string,
): JSXAttribute | null {
for (const attribute of attributes) {
for (const attribute of node.openingElement.attributes) {
if (isJSXAttribute(attribute) && attribute.name.name === name) {
return attribute;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<fbt:list> 1`] = `
import { fbt } from "fbtee";
const x = fbt._(
/* __FBT__ start */ {
jsfbt: {
m: [],
t: { desc: "Lists", text: "Available Locations: {locations}." },
},
project: "",
} /* __FBT__ end */,
[fbt._list("locations", ["Tokyo", "London", "Vienna"])]
);
`;

exports[`<fbt:list> 2`] = `
import { fbt } from "fbtee";
const x = fbt._(
/* __FBT__ start */ {
jsfbt: {
m: [],
t: { desc: "Lists", text: "Available Locations: {locations}." },
},
project: "",
} /* __FBT__ end */,
[fbt._list("locations", ["Tokyo", "London", "Vienna"], "and")]
);
`;

exports[`<fbt:list> 3`] = `
import { fbt } from "fbtee";
const x = fbt._(
/* __FBT__ start */ {
jsfbt: {
m: [],
t: { desc: "Lists", text: "Available Locations: {locations}." },
},
project: "",
} /* __FBT__ end */,
[fbt._list("locations", ["Tokyo", "London", "Vienna"], null, "bullet")]
);
`;

exports[`<fbt:list> 4`] = `
import { fbt } from "fbtee";
const x = fbt._(
/* __FBT__ start */ {
jsfbt: {
m: [],
t: { desc: "Lists", text: "Available Locations: {locations}." },
},
project: "",
} /* __FBT__ end */,
[fbt._list("locations", ["Tokyo", "London", "Vienna"], "or", "bullet")]
);
`;

exports[`fbt.list() 1`] = `
import { fbt } from "fbtee";
fbt._(
/* __FBT__ start */ {
jsfbt: {
m: [],
t: { desc: "Lists", text: "Available Locations: {locations}" },
},
project: "",
} /* __FBT__ end */,
[fbt._list("locations", ["Tokyo", "London", "Vienna"], null, "or")]
);
`;
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test Fbt Enum should handle functional enums (with references) (import default) 1`] = `
import { fbt } from "fbt";
import { fbt } from "fbtee";
import aEnum from "Test$FbtEnum";
var x = fbt._(
/* __FBT__ start */ {
Expand All @@ -21,7 +21,7 @@ var x = fbt._(
`;

exports[`Test Fbt Enum should handle functional enums (with references) (import star) 1`] = `
import { fbt } from "fbt";
import { fbt } from "fbtee";
import * as aEnum from "Test$FbtEnum";
var x = fbt._(
/* __FBT__ start */ {
Expand Down
Loading

0 comments on commit 6c0e39e

Please sign in to comment.