diff --git a/.env.example b/.env.example index 9f5193c067..7f69b32b88 100644 --- a/.env.example +++ b/.env.example @@ -24,3 +24,4 @@ SOCIAL_AUTH_MICROMASTERS_LOGIN_URL= YOUTUBE_DEVELOPER_KEY= TIKA_SERVER_ENDPOINT=http://tika:9998/ TIKA_CLIENT_ONLY=True +WEBPACK_ANALYZE=True \ No newline at end of file diff --git a/frontends/infinite-corridor/package.json b/frontends/infinite-corridor/package.json index f30bf9cb62..1924308a68 100644 --- a/frontends/infinite-corridor/package.json +++ b/frontends/infinite-corridor/package.json @@ -31,7 +31,6 @@ "ol-util": "workspace:*", "ol-widgets": "workspace:*", "postcss-loader": "^7.0.1", - "ramda": "^0.28.0", "react": "^16.14", "react-dom": "^16.14", "react-helmet-async": "^1.3.0", @@ -44,6 +43,7 @@ "swc-loader": "^0.2.3", "typescript": "^4.7.3", "webpack": "^5.71.0", + "webpack-bundle-analyzer": "^4.6.1", "webpack-bundle-tracker": "^1.4.0", "webpack-cli": "^4.9.2", "webpack-dev-middleware": "^5.3.1", diff --git a/frontends/infinite-corridor/src/api/fields/factories.ts b/frontends/infinite-corridor/src/api/fields/factories.ts index 7794282101..4a2cb89260 100644 --- a/frontends/infinite-corridor/src/api/fields/factories.ts +++ b/frontends/infinite-corridor/src/api/fields/factories.ts @@ -1,7 +1,8 @@ import { faker } from "@faker-js/faker" -import * as R from "ramda" -import { makePaginatedFactory, Factory } from "ol-util" -import { LearningResourceType, factories } from "ol-search-ui" +import { makePaginatedFactory, Factory } from "ol-util/build/factories" +import { LearningResourceType } from "ol-search-ui" +import * as factories from "ol-search-ui/build/factories" +import { times } from "lodash" import type { FieldChannel, UserList, UserListItem } from "./interfaces" export const makeUserList: Factory = overrides => { @@ -10,7 +11,7 @@ export const makeUserList: Factory = overrides => { short_description: faker.lorem.paragraph(), offered_by: [], title: faker.lorem.words(), - topics: R.times(() => factories.makeTopic(), 2), + topics: times(2, () => factories.makeTopic()), is_favorite: faker.datatype.boolean(), image_src: new URL(faker.internet.url()).toString(), image_description: faker.helpers.arrayElement([ diff --git a/frontends/infinite-corridor/src/pages/HomePage.test.tsx b/frontends/infinite-corridor/src/pages/HomePage.test.tsx index 0a95c53c5f..def501d4d6 100644 --- a/frontends/infinite-corridor/src/pages/HomePage.test.tsx +++ b/frontends/infinite-corridor/src/pages/HomePage.test.tsx @@ -2,7 +2,7 @@ import { assertInstanceOf, assertNotNil } from "ol-util" import { zip } from "lodash" import { FieldChannel, urls } from "../api/fields" import { urls as widgetUrls } from "../api/widgets" -import { makeWidgetListResponse } from "ol-widgets" +import { makeWidgetListResponse } from "ol-widgets/build/factories" import * as factories from "../api/fields/factories" import { screen, diff --git a/frontends/infinite-corridor/src/pages/SearchPage.test.tsx b/frontends/infinite-corridor/src/pages/SearchPage.test.tsx index 71493312fb..5baeae8063 100644 --- a/frontends/infinite-corridor/src/pages/SearchPage.test.tsx +++ b/frontends/infinite-corridor/src/pages/SearchPage.test.tsx @@ -1,6 +1,6 @@ import { when } from "jest-when" -import { factories } from "ol-search-ui" +import { makeSearchResponse } from "ol-search-ui/build/factories" import { buildSearchQuery } from "@mitodl/course-search-utils" import { assertInstanceOf } from "ol-util" @@ -9,8 +9,6 @@ import { screen, renderTestApp, setMockResponse, user } from "../test-utils" import { fireEvent, waitFor } from "@testing-library/react" import { makeRequest } from "../test-utils/mockAxios" -const { makeSearchResponse } = factories - const expectedFacets = { audience: [], certification: [], diff --git a/frontends/infinite-corridor/src/pages/SearchPage.tsx b/frontends/infinite-corridor/src/pages/SearchPage.tsx index 30bd571f61..75cbd16510 100644 --- a/frontends/infinite-corridor/src/pages/SearchPage.tsx +++ b/frontends/infinite-corridor/src/pages/SearchPage.tsx @@ -2,7 +2,7 @@ import React, { useState, useCallback } from "react" import Container from "@mui/material/Container" import Grid from "@mui/material/Grid" -import { intersection } from "ramda" +import { intersection } from "lodash" import { BannerPage, useDeviceCategory, DESKTOP } from "ol-util" import InfiniteScroll from "react-infinite-scroller" import { @@ -76,7 +76,7 @@ const SearchPage: React.FC = () => { const runSearch = useCallback( async (text: string, activeFacets: Facets, from: number) => { if (activeFacets["type"]) { - activeFacets["type"] = intersection(activeFacets["type"], ALLOWED_TYPES) + activeFacets["type"] = intersection(ALLOWED_TYPES, activeFacets["type"]) } else { activeFacets["type"] = ALLOWED_TYPES } diff --git a/frontends/infinite-corridor/src/pages/field-details/EditFieldAppearanceForm.test.tsx b/frontends/infinite-corridor/src/pages/field-details/EditFieldAppearanceForm.test.tsx index 2bca396808..df9483229f 100644 --- a/frontends/infinite-corridor/src/pages/field-details/EditFieldAppearanceForm.test.tsx +++ b/frontends/infinite-corridor/src/pages/field-details/EditFieldAppearanceForm.test.tsx @@ -10,7 +10,7 @@ import { FieldChannel, urls } from "../../api/fields" import { urls as widgetUrls } from "../../api/widgets" import { waitFor } from "@testing-library/react" import { makeFieldViewPath } from "../urls" -import { makeWidgetListResponse } from "ol-widgets" +import { makeWidgetListResponse } from "ol-widgets/build/factories" const setupApis = (fieldOverrides?: Partial) => { const field = factory.makeField({ is_moderator: true, ...fieldOverrides }) diff --git a/frontends/infinite-corridor/src/pages/field-details/EditFieldBasicForm.test.tsx b/frontends/infinite-corridor/src/pages/field-details/EditFieldBasicForm.test.tsx index 3551aea0dc..3f8fa9022e 100644 --- a/frontends/infinite-corridor/src/pages/field-details/EditFieldBasicForm.test.tsx +++ b/frontends/infinite-corridor/src/pages/field-details/EditFieldBasicForm.test.tsx @@ -7,7 +7,7 @@ import * as factory from "../../api/fields/factories" import { DEFAULT_PAGE_SIZE } from "../../api/fields/urls" import { makeFieldViewPath } from "../urls" import { renderTestApp, screen, setMockResponse, user } from "../../test-utils" -import { makeWidgetListResponse } from "ol-widgets" +import { makeWidgetListResponse } from "ol-widgets/build/factories" describe("EditFieldBasicForm", () => { let field: FieldChannel, publicLists: PaginatedResult diff --git a/frontends/infinite-corridor/src/pages/field-details/FieldPage.test.tsx b/frontends/infinite-corridor/src/pages/field-details/FieldPage.test.tsx index b3a0e53300..07a6c10fa7 100644 --- a/frontends/infinite-corridor/src/pages/field-details/FieldPage.test.tsx +++ b/frontends/infinite-corridor/src/pages/field-details/FieldPage.test.tsx @@ -14,7 +14,7 @@ import { user, waitFor } from "../../test-utils" -import { makeWidgetListResponse } from "ol-widgets" +import { makeWidgetListResponse } from "ol-widgets/build/factories" jest.mock("./WidgetsList", () => { const actual = jest.requireActual("./WidgetsList") diff --git a/frontends/infinite-corridor/src/pages/field-details/WidgetsList.test.tsx b/frontends/infinite-corridor/src/pages/field-details/WidgetsList.test.tsx index 65db94e6e6..c54a90d235 100644 --- a/frontends/infinite-corridor/src/pages/field-details/WidgetsList.test.tsx +++ b/frontends/infinite-corridor/src/pages/field-details/WidgetsList.test.tsx @@ -6,7 +6,8 @@ import { expectProps, user } from "../../test-utils" -import { Widget, makeWidgetListResponse, WidgetsListEditable } from "ol-widgets" +import { Widget, WidgetsListEditable } from "ol-widgets" +import { makeWidgetListResponse } from "ol-widgets/build/factories" import WidgetsList from "./WidgetsList" import { setMockResponse } from "../../test-utils" import { urls } from "../../api/widgets" diff --git a/frontends/infinite-corridor/src/scss/admin.scss b/frontends/infinite-corridor/src/scss/admin.scss index 2735174b7e..7c47d76083 100644 --- a/frontends/infinite-corridor/src/scss/admin.scss +++ b/frontends/infinite-corridor/src/scss/admin.scss @@ -78,10 +78,10 @@ .three-cols { td { - width: 3 * 100% / 8; + width: 3 * 100% * 0.125; &:last-child { - width: 2 * 100% / 8; + width: 2 * 100% * 0.125; } } } diff --git a/frontends/infinite-corridor/src/scss/fieldpage.scss b/frontends/infinite-corridor/src/scss/fieldpage.scss index 86cf3acf30..8313e52eb3 100644 --- a/frontends/infinite-corridor/src/scss/fieldpage.scss +++ b/frontends/infinite-corridor/src/scss/fieldpage.scss @@ -31,8 +31,8 @@ $avatarSize: 60px; $carouselSpacing: 24px; .ic-carousel-card { height:100%; - margin-left: $carouselSpacing/2; - margin-right: $carouselSpacing/2; + margin-left: $carouselSpacing*0.5; + margin-right: $carouselSpacing*0.5; } .ic-carousel { @@ -56,7 +56,7 @@ $carouselSpacing: 24px; Caveat: This is not a good solution if there is content within $carouselSpacing of the carousel's left edge. But...there's not. */ - transform: translateX(-$carouselSpacing/2); + transform: translateX(-$carouselSpacing*0.5); /* We also want the carousel contents (cards) to appear as if they are full width. By default, the width is 100% and since there's cell-spacing, the diff --git a/frontends/infinite-corridor/webpack.config.js b/frontends/infinite-corridor/webpack.config.js index 407e5fe5ea..5b492e7afd 100644 --- a/frontends/infinite-corridor/webpack.config.js +++ b/frontends/infinite-corridor/webpack.config.js @@ -4,6 +4,8 @@ const webpack = require("webpack") const BundleTracker = require("webpack-bundle-tracker") const MiniCssExtractPlugin = require("mini-css-extract-plugin") const CKEditorWebpackPlugin = require('@ckeditor/ckeditor5-dev-webpack-plugin') +const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') + const { styles } = require('@ckeditor/ckeditor5-dev-utils') const STATS_FILEPATH = path.resolve(__dirname, "../../webpack-stats/infinite-corridor.json") @@ -60,7 +62,7 @@ const ckeditorRules = [ ] -const getWebpackConfig = mode => { +const getWebpackConfig = ({mode, analyzeBundle}) => { const isProduction = mode === "production" const publicPath = getPublicPath(isProduction) return { @@ -123,7 +125,11 @@ const getWebpackConfig = mode => { language: "en", addMainLanguageTranslationsToAllAssets: true }) - ] : []), + ] : []).concat( + analyzeBundle ? [new BundleAnalyzerPlugin({ + analyzerMode: "static", + })] : [] + ), resolve: { extensions: [".js", ".jsx", ".ts", ".tsx"] }, @@ -160,5 +166,7 @@ const getWebpackConfig = mode => { module.exports = (_env, argv) => { const mode = argv.mode || process.env.NODE_ENV || "production" - return getWebpackConfig(mode) + const analyzeBundle = process.env.WEBPACK_ANALYZE === "True" + const settings = { mode, analyzeBundle } + return getWebpackConfig(settings) } diff --git a/frontends/ol-forms/package.json b/frontends/ol-forms/package.json index 139a1c3259..5af82660fd 100644 --- a/frontends/ol-forms/package.json +++ b/frontends/ol-forms/package.json @@ -11,6 +11,7 @@ "@dnd-kit/sortable": "^7.0.1", "@dnd-kit/utilities": "^3.2.0", "formik": "^2.2.9", + "lodash": "^4.17.21", "react-select": "^5.4.0", "yup": "^0.32.11" } diff --git a/frontends/ol-forms/src/components/SelectField.tsx b/frontends/ol-forms/src/components/SelectField.tsx index 8b30cd6adc..c53096b928 100644 --- a/frontends/ol-forms/src/components/SelectField.tsx +++ b/frontends/ol-forms/src/components/SelectField.tsx @@ -5,7 +5,7 @@ import Select, { SingleValue } from "react-select" import AsyncSelect from "react-select/async" -import { isNil } from "ramda" +import { isNil } from "lodash" export interface Option { label: string diff --git a/frontends/ol-search-ui/assets/learning-resource-card.scss b/frontends/ol-search-ui/assets/learning-resource-card.scss index 3591629bd7..224eea2c89 100644 --- a/frontends/ol-search-ui/assets/learning-resource-card.scss +++ b/frontends/ol-search-ui/assets/learning-resource-card.scss @@ -1,3 +1,4 @@ +@use "sass:math"; $lightText: #8c8c8c !default; $spacer: 0.75rem !default; @@ -52,8 +53,8 @@ $smallFontSize: 0.75em !default; The column-gap property would be a nicer solution, but it doesn't have the best browser support yet. */ - margin-top: $spacer/2; - margin-bottom: $spacer/2; + margin-top: math.div($spacer, 2); + margin-bottom: math.div($spacer, 2); &:first-child { margin-top: 0; } diff --git a/frontends/ol-search-ui/package.json b/frontends/ol-search-ui/package.json index 9136715aa9..7c4b696287 100644 --- a/frontends/ol-search-ui/package.json +++ b/frontends/ol-search-ui/package.json @@ -8,6 +8,7 @@ "@faker-js/faker": "^7.3.0", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.0", + "lodash": "^4.17.21", "ol-forms": "workspace:*", "ol-util": "workspace:*" }, diff --git a/frontends/ol-search-ui/src/components/ExpandedLearningResourceDisplay.tsx b/frontends/ol-search-ui/src/components/ExpandedLearningResourceDisplay.tsx index 5952627bf1..972c97d530 100644 --- a/frontends/ol-search-ui/src/components/ExpandedLearningResourceDisplay.tsx +++ b/frontends/ol-search-ui/src/components/ExpandedLearningResourceDisplay.tsx @@ -1,9 +1,9 @@ import React from "react" import striptags from "striptags" import { decode } from "html-entities" +import { emptyOrNil } from "ol-util" import TruncatedText from "./TruncatedText" -import R from "ramda" import ShareTooltip from "./ShareTooltip" @@ -13,7 +13,6 @@ import { minPrice, getStartDate, getInstructorName, - emptyOrNil, languageName, resourceThumbnailSrc, CertificateIcon, @@ -158,7 +157,7 @@ export default function ExpandedLearningResourceDisplay(props: Props) { {cost ? lrInfoRow("Cost:", cost) : lrInfoRow("Cost:", "Free")} {selectedRun?.level ? lrInfoRow("Level:", selectedRun.level) : null} {!emptyOrNil(instructors) ? - lrInfoRow("Instructors:", R.join(", ", instructors)) : + lrInfoRow("Instructors:", instructors.join(", ")) : null} {object.object_type === LearningResourceType.Program && object.item_count ? diff --git a/frontends/ol-search-ui/src/components/Facet.tsx b/frontends/ol-search-ui/src/components/Facet.tsx index d02a147920..c0b03b5d1d 100644 --- a/frontends/ol-search-ui/src/components/Facet.tsx +++ b/frontends/ol-search-ui/src/components/Facet.tsx @@ -1,5 +1,4 @@ import React, { useState } from "react" -import { contains } from "ramda" import SearchFacetItem from "./SearchFacetItem" import { Aggregation } from "@mitodl/course-search-utils" @@ -34,7 +33,7 @@ function SearchFacet(props: Props) { diff --git a/frontends/ol-search-ui/src/factories.ts b/frontends/ol-search-ui/src/factories.ts index 9fc1ae50c4..1722824343 100644 --- a/frontends/ol-search-ui/src/factories.ts +++ b/frontends/ol-search-ui/src/factories.ts @@ -1,9 +1,8 @@ //@ts-expect-error casual-browserify does not have typescript types import casual from "casual-browserify" import { faker } from "@faker-js/faker" -import R from "ramda" import { DATE_FORMAT } from "./util" -import { Factory } from "ol-util" +import { Factory } from "ol-util/build/factories" import { CourseTopic, LearningResourceResult, @@ -13,7 +12,7 @@ import { CardMinimalResource, EmbedlyConfig } from "./interfaces" -import { pick } from "lodash" +import { pick, times } from "lodash" const OPEN_CONTENT = "Open Content" const PROFESSIONAL = "Professional Offerings" @@ -51,7 +50,7 @@ export const makeCourseResult: Factory = overrides => ({ offered_by: [casual.random_element(["edx", "ocw"])], topics: [casual.word, casual.word], object_type: LearningResourceType.Course, - runs: R.times(() => makeRun(), 3), + runs: times(3, () => makeRun()), is_favorite: casual.coin_flip, lists: casual.random_element([[], [100, 200]]), audience: casual.random_element([ @@ -127,7 +126,7 @@ export const makeSearchResponse = ( type: string | null = null, withFacets = true ) => { - const hits = R.times(() => makeSearchResult(type), pageSize) + const hits = times(pageSize, () => makeSearchResult(type)) return { hits: { total, @@ -175,10 +174,10 @@ export const makeLearningResource: Factory = overrides => { id: faker.unique(faker.datatype.number), title: faker.lorem.words(), image_src: new URL(faker.internet.url()).toString(), - topics: R.times(() => makeTopic(), 2), + topics: times(2, () => makeTopic()), object_type: makeLearningResourceType(), platform: faker.lorem.word(), - runs: R.times(() => makeRun(), 3), + runs: times(3, () => makeRun()), lists: [], ...overrides } diff --git a/frontends/ol-search-ui/src/index.ts b/frontends/ol-search-ui/src/index.ts index bd995ad32e..e17faea2e3 100644 --- a/frontends/ol-search-ui/src/index.ts +++ b/frontends/ol-search-ui/src/index.ts @@ -1,3 +1,2 @@ export * from "./components" export * from "./interfaces" -export * as factories from "./factories" diff --git a/frontends/ol-search-ui/src/util.tsx b/frontends/ol-search-ui/src/util.tsx index 62c84df055..e1f98a855d 100644 --- a/frontends/ol-search-ui/src/util.tsx +++ b/frontends/ol-search-ui/src/util.tsx @@ -7,10 +7,9 @@ import { LearningResourceType as LR } from "./interfaces" import React, { useState, useEffect } from "react" -import R from "ramda" +import { capitalize, emptyOrNil } from "ol-util" import LocaleCode from "locale-code" import Decimal from "decimal.js-light" -import { F } from "ts-toolbelt" export const getImageSrc = ( resource: { image_src?: string | null; platform?: string | null }, @@ -196,8 +195,6 @@ export const getInstructorName = (instructor: CourseInstructor) => { return "" } -export const emptyOrNil = R.either(R.isEmpty, R.isNil) - export const languageName = (langCode: string | null): string => LocaleCode.getLanguageName( `${langCode ? langCode.split("-")[0].toLowerCase() : "en"}-US` @@ -221,9 +218,3 @@ const formatPrice = (price: number | null | undefined): string => { export const absolutizeURL = (url: string) => new URL(url, window.location.origin).toString() - -// @ts-expect-error typescript complains about getting 0 arguments -export const capitalize = R.converge(R.concat(), [ - R.compose(R.toUpper, R.head), - R.tail -]) as F.Curry<(text: string) => string> diff --git a/frontends/ol-util/package.json b/frontends/ol-util/package.json index 1cfbe77b62..9b5ac6295d 100644 --- a/frontends/ol-util/package.json +++ b/frontends/ol-util/package.json @@ -13,8 +13,8 @@ "@dnd-kit/utilities": "^3.2.0", "@faker-js/faker": "^7.3.0", "classnames": "^2.3.1", + "lodash": "^4.17.21", "nuka-carousel": "^5.2.0", - "qs": "^6.11.0", - "ramda": "^0.28.0" + "qs": "^6.11.0" } } diff --git a/frontends/ol-util/src/factories.ts b/frontends/ol-util/src/factories.ts index 771b006569..f5b77cd16a 100644 --- a/frontends/ol-util/src/factories.ts +++ b/frontends/ol-util/src/factories.ts @@ -1,4 +1,5 @@ import type { PaginatedResult } from "./interfaces" +import { times } from "lodash" type Factory = (overrides?: Partial, options?: U) => T @@ -14,9 +15,7 @@ const makePaginatedFactory = previous?: string | null } = {} ): PaginatedResult => { - const results = Array(count) - .fill(null) - .map(() => makeResult()) + const results = times(count, () => makeResult()) return { results, count, diff --git a/frontends/ol-util/src/index.ts b/frontends/ol-util/src/index.ts index e13a8e4265..2224de6bab 100644 --- a/frontends/ol-util/src/index.ts +++ b/frontends/ol-util/src/index.ts @@ -5,6 +5,5 @@ export * from "./styles" export * from "./predicates" export * from "./hooks" export * from "./interfaces" -export * from "./factories" export * from "./querystrings" export * from "./lib" diff --git a/frontends/ol-util/src/lib/index.ts b/frontends/ol-util/src/lib/index.ts index e2fd6d51f0..e260f7f92a 100644 --- a/frontends/ol-util/src/lib/index.ts +++ b/frontends/ol-util/src/lib/index.ts @@ -4,5 +4,6 @@ export { useDeviceCategory, PHONE, TABLET, - DESKTOP + DESKTOP, + emptyOrNil } from "./utils" diff --git a/frontends/ol-util/src/lib/utils.test.ts b/frontends/ol-util/src/lib/utils.test.ts new file mode 100644 index 0000000000..2022a5f5a8 --- /dev/null +++ b/frontends/ol-util/src/lib/utils.test.ts @@ -0,0 +1,51 @@ +import * as u from "./utils" + +describe("capitalize", () => { + it("Capitalizes the first letter of the the string", () => { + expect(u.capitalize("hello world")).toBe("Hello world") + }) + it("Does nothing to the empty string", () => { + expect(u.capitalize("")).toBe("") + }) +}) + +describe("Initials", () => { + it.each([ + { in: "ant bat cat", out: "AB" }, + { in: "dog Elephant frog", out: "DE" }, + { in: "goat", out: "G" }, + { in: "Horse", out: "H" }, + { in: " iguana jackal", out: "IJ" }, + { in: "", out: "" } + ])("Gets the capitalized first letter of the first two words", testcase => { + expect(u.initials(testcase.in)).toBe(testcase.out) + }) +}) + +describe("emptyOrNil", () => { + it("Returns true for null and undefined", () => { + expect(u.emptyOrNil(undefined)).toBe(true) + expect(u.emptyOrNil(null)).toBe(true) + }) + + it("Returns true for empty objects, strings, sets, and arrays, and maps", () => { + expect(u.emptyOrNil("")).toBe(true) + expect(u.emptyOrNil([])).toBe(true) + expect(u.emptyOrNil(new Set())).toBe(true) + expect(u.emptyOrNil({})).toBe(true) + expect(u.emptyOrNil(new Map())).toBe(true) + }) + + it("Returns true for numbers", () => { + // _.isEmpty(5) returns true; this is different from ramda. + expect(u.emptyOrNil(5)).toBe(true) + }) + + it("Returns false for and non-empty objects, strings, sets, and arrays, and maps", () => { + expect(u.emptyOrNil("a")).toBe(false) + expect(u.emptyOrNil([10])).toBe(false) + expect(u.emptyOrNil(new Set([10]))).toBe(false) + expect(u.emptyOrNil({ a: 10 })).toBe(false) + expect(u.emptyOrNil(new Map([["a", 10]]))).toBe(false) + }) +}) diff --git a/frontends/ol-util/src/lib/utils.ts b/frontends/ol-util/src/lib/utils.ts index 7a1b695f3d..2df7eceb07 100644 --- a/frontends/ol-util/src/lib/utils.ts +++ b/frontends/ol-util/src/lib/utils.ts @@ -1,17 +1,18 @@ -import * as R from "ramda" import { useState, useEffect } from "react" - -export const initials: (title: string) => string = R.pipe( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - R.split(/\s+/), - R.slice(0, 2), - R.map((item: string) => (item ? item[0].toUpperCase() : "")), - R.join("") -) +import isEmpty from "lodash/isEmpty" +import isNil from "lodash/isNil" + +export const initials = (title: string): string => { + return title + .trim() + .split(/\s+/) + .slice(0, 2) + .map(item => (item[0] ?? "").toUpperCase()) + .join("") +} export const capitalize = (txt: string) => - txt[0].toUpperCase() + txt.slice(1).toLowerCase() + (txt[0] ?? "").toUpperCase() + txt.slice(1).toLowerCase() export const PHONE = "PHONE" export const TABLET = "TABLET" @@ -49,3 +50,5 @@ export const useDeviceCategory = () => { } return DESKTOP } + +export const emptyOrNil = (x: unknown): boolean => isNil(x) || isEmpty(x) diff --git a/frontends/ol-widgets/src/factories.ts b/frontends/ol-widgets/src/factories.ts index f6fe317bf2..1944bc2c55 100644 --- a/frontends/ol-widgets/src/factories.ts +++ b/frontends/ol-widgets/src/factories.ts @@ -1,5 +1,6 @@ import { faker } from "@faker-js/faker" -import type { Factory } from "ol-util" +import { Factory } from "ol-util/build/factories" +import { times } from "lodash" import type { WidgetInstance, RichTextWidgetInstance, @@ -76,9 +77,7 @@ const makeWidgetListResponse: Factory< return { id: faker.datatype.number(), available_widgets: specMakers.map(f => f()), - widgets: Array(count) - .fill(null) - .map(() => makeWidget()), + widgets: times(count, () => makeWidget()), ...overrides } } diff --git a/frontends/ol-widgets/src/index.ts b/frontends/ol-widgets/src/index.ts index 451b0a7b4d..640ac48791 100644 --- a/frontends/ol-widgets/src/index.ts +++ b/frontends/ol-widgets/src/index.ts @@ -1,3 +1,2 @@ export * from "./interfaces" export * from "./components" -export * from "./factories" diff --git a/frontends/open-discussions/package.json b/frontends/open-discussions/package.json index 04451ae9bf..b2306ad0f2 100644 --- a/frontends/open-discussions/package.json +++ b/frontends/open-discussions/package.json @@ -157,6 +157,7 @@ "uuid": "^8.3.2", "validator": "13.7.0", "webpack": "5.73.0", + "webpack-bundle-analyzer": "^4.6.1", "webpack-bundle-tracker": "1.5.0", "webpack-cli": "4.10.0", "webpack-dev-middleware": "5.3.3", diff --git a/frontends/open-discussions/webpack.config.js b/frontends/open-discussions/webpack.config.js index 114b91f434..f97b8dd743 100644 --- a/frontends/open-discussions/webpack.config.js +++ b/frontends/open-discussions/webpack.config.js @@ -2,6 +2,7 @@ const path = require("path") const webpack = require("webpack") const BundleTracker = require("webpack-bundle-tracker") const MiniCssExtractPlugin = require("mini-css-extract-plugin") +const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') const STATS_FILEPATH = path.resolve(__dirname, "../../webpack-stats/open-discussions.json") @@ -16,7 +17,7 @@ const getPublicPath = isProduction => { } -const getWebpackConfig = mode => { +const getWebpackConfig = ({mode, analyzeBundle}) => { console.log(`Building for ${mode}`) const isProduction = mode === "production" const publicPath = getPublicPath(isProduction) @@ -73,7 +74,11 @@ const getWebpackConfig = mode => { new webpack.LoaderOptionsPlugin({ minimize: true }), new webpack.optimize.AggressiveMergingPlugin(), new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" }) - ] : []), + ] : []).concat( + analyzeBundle ? [new BundleAnalyzerPlugin({ + analyzerMode: "static", + })] : [] + ), resolve: { extensions: [".js", ".jsx"], fallback: { @@ -122,5 +127,6 @@ const getWebpackConfig = mode => { module.exports = (_env, argv) => { const mode = argv.mode || process.env.NODE_ENV || "production" - return getWebpackConfig(mode) + const analyzeBundle = process.env.WEBPACK_ANALYZE === "True" + return getWebpackConfig({mode, analyzeBundle}) } diff --git a/package.json b/package.json index f64740f23a..1a75340024 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@types/enzyme-adapter-react-16": "^1.0.6", "@types/jest": "^28.1.3", "@types/jest-when": "^3.5.2", - "@types/ramda": "^0.28.15", "@types/react-dotdotdot": "^1.2.5", "@types/react-infinite-scroller": "^1.2.3", "@types/styled-components": "^5.1.25", diff --git a/yarn.lock b/yarn.lock index c31db7f23d..8fbf35feb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4260,6 +4260,13 @@ __metadata: languageName: node linkType: hard +"@polka/url@npm:^1.0.0-next.20": + version: 1.0.0-next.21 + resolution: "@polka/url@npm:1.0.0-next.21" + checksum: c7654046d38984257dd639eab3dc770d1b0340916097b2fac03ce5d23506ada684e05574a69b255c32ea6a144a957c8cd84264159b545fca031c772289d88788 + languageName: node + linkType: hard + "@popperjs/core@npm:^2.11.5": version: 2.11.5 resolution: "@popperjs/core@npm:2.11.5" @@ -6255,15 +6262,6 @@ __metadata: languageName: node linkType: hard -"@types/ramda@npm:^0.28.15": - version: 0.28.15 - resolution: "@types/ramda@npm:0.28.15" - dependencies: - ts-toolbelt: ^6.15.1 - checksum: f96576c78a3bfdc8d0675a7c5fb237a56fe76b1d380578f63e59185bd1938c38729d7051d92750e6773bc2ea9c64e5d63bdbd7b95c92f5d400d3d5ce850a6dbd - languageName: node - linkType: hard - "@types/range-parser@npm:*": version: 1.2.4 resolution: "@types/range-parser@npm:1.2.4" @@ -6962,7 +6960,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.1.1": +"acorn-walk@npm:^8.0.0, acorn-walk@npm:^8.1.1": version: 8.2.0 resolution: "acorn-walk@npm:8.2.0" checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 @@ -6996,6 +6994,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.0.4": + version: 8.8.0 + resolution: "acorn@npm:8.8.0" + bin: + acorn: bin/acorn + checksum: 7270ca82b242eafe5687a11fea6e088c960af712683756abf0791b68855ea9cace3057bd5e998ffcef50c944810c1e0ca1da526d02b32110e13c722aa959afdc + languageName: node + linkType: hard + "acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.1": version: 8.7.1 resolution: "acorn@npm:8.7.1" @@ -10034,6 +10041,13 @@ __metadata: languageName: node linkType: hard +"duplexer@npm:^0.1.2": + version: 0.1.2 + resolution: "duplexer@npm:0.1.2" + checksum: 62ba61a830c56801db28ff6305c7d289b6dc9f859054e8c982abd8ee0b0a14d2e9a8e7d086ffee12e868d43e2bbe8a964be55ddbd8c8957714c87373c7a4f9b0 + languageName: node + linkType: hard + "editorconfig@npm:^0.15.3": version: 0.15.3 resolution: "editorconfig@npm:0.15.3" @@ -12274,6 +12288,15 @@ __metadata: languageName: node linkType: hard +"gzip-size@npm:^6.0.0": + version: 6.0.0 + resolution: "gzip-size@npm:6.0.0" + dependencies: + duplexer: ^0.1.2 + checksum: 2df97f359696ad154fc171dcb55bc883fe6e833bca7a65e457b9358f3cb6312405ed70a8da24a77c1baac0639906cd52358dc0ce2ec1a937eaa631b934c94194 + languageName: node + linkType: hard + "handle-thing@npm:^2.0.0": version: 2.0.1 resolution: "handle-thing@npm:2.0.1" @@ -12850,7 +12873,6 @@ __metadata: ol-util: "workspace:*" ol-widgets: "workspace:*" postcss-loader: ^7.0.1 - ramda: ^0.28.0 react: ^16.14 react-dom: ^16.14 react-helmet-async: ^1.3.0 @@ -12863,6 +12885,7 @@ __metadata: swc-loader: ^0.2.3 typescript: ^4.7.3 webpack: ^5.71.0 + webpack-bundle-analyzer: ^4.6.1 webpack-bundle-tracker: ^1.4.0 webpack-cli: ^4.9.2 webpack-dev-middleware: ^5.3.1 @@ -15071,7 +15094,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.0.0, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.3.0, lodash@npm:^4.7.0, lodash@npm:^4.9.0, lodash@npm:~4.17.10": +"lodash@npm:^4.0.0, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.3.0, lodash@npm:^4.7.0, lodash@npm:^4.9.0, lodash@npm:~4.17.10": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -16039,6 +16062,13 @@ __metadata: languageName: node linkType: hard +"mrmime@npm:^1.0.0": + version: 1.0.1 + resolution: "mrmime@npm:1.0.1" + checksum: cc979da44bbbffebaa8eaf7a45117e851f2d4cb46a3ada6ceb78130466a04c15a0de9a9ce1c8b8ba6f6e1b8618866b1352992bf1757d241c0ddca558b9f28a77 + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -16592,6 +16622,7 @@ __metadata: "@dnd-kit/sortable": ^7.0.1 "@dnd-kit/utilities": ^3.2.0 formik: ^2.2.9 + lodash: ^4.17.21 react-select: ^5.4.0 yup: ^0.32.11 peerDependencies: @@ -16609,6 +16640,7 @@ __metadata: "@faker-js/faker": ^7.3.0 "@mui/icons-material": ^5.8.4 "@mui/material": ^5.9.0 + lodash: ^4.17.21 ol-forms: "workspace:*" ol-util: "workspace:*" peerDependencies: @@ -16626,9 +16658,9 @@ __metadata: "@dnd-kit/utilities": ^3.2.0 "@faker-js/faker": ^7.3.0 classnames: ^2.3.1 + lodash: ^4.17.21 nuka-carousel: ^5.2.0 qs: ^6.11.0 - ramda: ^0.28.0 peerDependencies: react: ^16.10 || 17 || 18 react-router: ^5 || ^6 @@ -16733,7 +16765,6 @@ __metadata: "@types/enzyme-adapter-react-16": ^1.0.6 "@types/jest": ^28.1.3 "@types/jest-when": ^3.5.2 - "@types/ramda": ^0.28.15 "@types/react-dotdotdot": ^1.2.5 "@types/react-infinite-scroller": ^1.2.3 "@types/styled-components": ^5.1.25 @@ -16925,6 +16956,7 @@ __metadata: uuid: ^8.3.2 validator: 13.7.0 webpack: 5.73.0 + webpack-bundle-analyzer: ^4.6.1 webpack-bundle-tracker: 1.5.0 webpack-cli: 4.10.0 webpack-dev-middleware: 5.3.3 @@ -16945,6 +16977,15 @@ __metadata: languageName: node linkType: hard +"opener@npm:^1.5.2": + version: 1.5.2 + resolution: "opener@npm:1.5.2" + bin: + opener: bin/opener-bin.js + checksum: 33b620c0d53d5b883f2abc6687dd1c5fd394d270dbe33a6356f2d71e0a2ec85b100d5bac94694198ccf5c30d592da863b2292c5539009c715a9c80c697b4f6cc + languageName: node + linkType: hard + "optionator@npm:^0.8.1": version: 0.8.3 resolution: "optionator@npm:0.8.3" @@ -18644,13 +18685,6 @@ __metadata: languageName: node linkType: hard -"ramda@npm:^0.28.0": - version: 0.28.0 - resolution: "ramda@npm:0.28.0" - checksum: 44ea6e5010bba70151b6a92d8114a91915e8b5a16105cce65fae58c9d7386b812c429645e35f21141d7087568550ce383bc10ee1a65cdec951f4b69ea457e6a4 - languageName: node - linkType: hard - "randexp@npm:0.4.6": version: 0.4.6 resolution: "randexp@npm:0.4.6" @@ -20844,6 +20878,17 @@ __metadata: languageName: node linkType: hard +"sirv@npm:^1.0.7": + version: 1.0.19 + resolution: "sirv@npm:1.0.19" + dependencies: + "@polka/url": ^1.0.0-next.20 + mrmime: ^1.0.0 + totalist: ^1.0.0 + checksum: c943cfc61baf85f05f125451796212ec35d4377af4da90ae8ec1fa23e6d7b0b4d9c74a8fbf65af83c94e669e88a09dc6451ba99154235eead4393c10dda5b07c + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -21938,6 +21983,13 @@ __metadata: languageName: node linkType: hard +"totalist@npm:^1.0.0": + version: 1.1.0 + resolution: "totalist@npm:1.1.0" + checksum: dfab80c7104a1d170adc8c18782d6c04b7df08352dec452191208c66395f7ef2af7537ddfa2cf1decbdcfab1a47afbbf0dec6543ea191da98c1c6e1599f86adc + languageName: node + linkType: hard + "tough-cookie@npm:^4.0.0": version: 4.0.0 resolution: "tough-cookie@npm:4.0.0" @@ -22101,13 +22153,6 @@ __metadata: languageName: node linkType: hard -"ts-toolbelt@npm:^6.15.1": - version: 6.15.5 - resolution: "ts-toolbelt@npm:6.15.5" - checksum: 24ad00cfd9ce735c76c873a9b1347eac475b94e39ebbdf100c9019dce88dd5f4babed52884cf82bb456a38c28edd0099ab6f704b84b2e5e034852b618472c1f3 - languageName: node - linkType: hard - "tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -22901,6 +22946,25 @@ __metadata: languageName: node linkType: hard +"webpack-bundle-analyzer@npm:^4.6.1": + version: 4.6.1 + resolution: "webpack-bundle-analyzer@npm:4.6.1" + dependencies: + acorn: ^8.0.4 + acorn-walk: ^8.0.0 + chalk: ^4.1.0 + commander: ^7.2.0 + gzip-size: ^6.0.0 + lodash: ^4.17.20 + opener: ^1.5.2 + sirv: ^1.0.7 + ws: ^7.3.1 + bin: + webpack-bundle-analyzer: lib/bin/analyzer.js + checksum: 4bc97ac6a1d9cd1f133444b0fc9d9091c97f4bd8388f97636ce27abd1ebffaa7dd45d29f6693661a666e77bcc08dff43ab7c2f5e2600a3101b956c94c1d038d0 + languageName: node + linkType: hard + "webpack-bundle-tracker@npm:1.5.0, webpack-bundle-tracker@npm:^1.4.0": version: 1.5.0 resolution: "webpack-bundle-tracker@npm:1.5.0" @@ -23393,9 +23457,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.4.5": - version: 7.5.8 - resolution: "ws@npm:7.5.8" +"ws@npm:^7.3.1, ws@npm:^7.4.6": + version: 7.5.9 + resolution: "ws@npm:7.5.9" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -23404,13 +23468,13 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 49479ccf3ddab6500c5906fbcc316e9c8cd44b0ffb3903a6c1caf9b38cb9e06691685722a4c642cfa7d4c6eb390424fc3142cd4f8b940cfc7a9ce9761b1cd65b + checksum: c3c100a181b731f40b7f2fddf004aa023f79d64f489706a28bc23ff88e87f6a64b3c6651fbec3a84a53960b75159574d7a7385709847a62ddb7ad6af76f49138 languageName: node linkType: hard -"ws@npm:^7.4.6": - version: 7.5.9 - resolution: "ws@npm:7.5.9" +"ws@npm:^7.4.5": + version: 7.5.8 + resolution: "ws@npm:7.5.8" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ^5.0.2 @@ -23419,7 +23483,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: c3c100a181b731f40b7f2fddf004aa023f79d64f489706a28bc23ff88e87f6a64b3c6651fbec3a84a53960b75159574d7a7385709847a62ddb7ad6af76f49138 + checksum: 49479ccf3ddab6500c5906fbcc316e9c8cd44b0ffb3903a6c1caf9b38cb9e06691685722a4c642cfa7d4c6eb390424fc3142cd4f8b940cfc7a9ce9761b1cd65b languageName: node linkType: hard