mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-19 18:18:27 -05:00
feat: dynamically generate search placeholder (#56276)
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
This commit is contained in:
committed by
GitHub
parent
45fb3774bd
commit
a378208d4d
@@ -2,6 +2,7 @@
|
||||
**/*fixtures*
|
||||
api-server/lib
|
||||
client/**/trending.json
|
||||
client/**/search-bar.json
|
||||
client/config/*.json
|
||||
client/config/browser-scripts/*.json
|
||||
client/static
|
||||
|
||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -14,6 +14,7 @@ static/curriculum-data
|
||||
# Generated config
|
||||
config/browser-scripts/*.json
|
||||
i18n/locales/**/trending.json
|
||||
i18n/locales/**/search-bar.json
|
||||
|
||||
# Config
|
||||
|
||||
|
||||
@@ -53,6 +53,13 @@ i18n.use(initReactI18next).init({
|
||||
if (clientLocale !== 'english') {
|
||||
module.exports = require('./locales/' + clientLocale + '/links.json');
|
||||
}
|
||||
`,
|
||||
'search-bar': preval`
|
||||
const envData = require('../config/env.json');
|
||||
const { clientLocale } = envData;
|
||||
if (clientLocale !== 'english') {
|
||||
module.exports = require('./locales/' + clientLocale + '/search-bar.json');
|
||||
}
|
||||
`
|
||||
},
|
||||
en: {
|
||||
@@ -60,10 +67,11 @@ i18n.use(initReactI18next).init({
|
||||
trending: preval`module.exports = require('./locales/english/trending.json')`,
|
||||
intro: preval`module.exports = require('./locales/english/intro.json')`,
|
||||
metaTags: preval`module.exports = require('./locales/english/meta-tags.json')`,
|
||||
links: preval`module.exports = require('./locales/english/links.json')`
|
||||
links: preval`module.exports = require('./locales/english/links.json')`,
|
||||
'search-bar': preval`module.exports = require('./locales/english/search-bar.json')`
|
||||
}
|
||||
},
|
||||
ns: ['translations', 'trending', 'intro', 'metaTags', 'links'],
|
||||
ns: ['translations', 'trending', 'intro', 'metaTags', 'links', 'search-bar'],
|
||||
defaultNS: 'translations',
|
||||
returnObjects: true,
|
||||
// Uncomment the next line for debug logging
|
||||
|
||||
@@ -692,7 +692,10 @@
|
||||
},
|
||||
"search": {
|
||||
"label": "Search",
|
||||
"placeholder": "Search 10,700+ tutorials",
|
||||
"placeholder": {
|
||||
"default": "Search our tutorials",
|
||||
"numbered": "Search {{ roundedTotalRecords }}+ tutorials"
|
||||
},
|
||||
"see-results": "See all results for {{searchQuery}}",
|
||||
"no-tutorials": "No tutorials found",
|
||||
"try": "Looking for something? Try the search bar on this page.",
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
"build": "NODE_OPTIONS=\"--max-old-space-size=7168\" gatsby build --prefix-paths",
|
||||
"build:scripts": "pnpm run -F=browser-scripts build",
|
||||
"clean": "gatsby clean",
|
||||
"common-setup": "pnpm -w run create:shared && pnpm run create:env && pnpm run create:trending",
|
||||
"common-setup": "pnpm -w run create:shared && pnpm run create:env && pnpm run create:trending && pnpm run create:search-placeholder",
|
||||
"create:env": "DEBUG=fcc:* ts-node ./tools/create-env.ts",
|
||||
"create:trending": "ts-node ./tools/download-trending.ts",
|
||||
"create:search-placeholder": "ts-node ./tools/generate-search-placeholder",
|
||||
"predevelop": "pnpm run common-setup && pnpm run build:scripts --env development",
|
||||
"develop": "NODE_OPTIONS=\"--max-old-space-size=7168\" gatsby develop --inspect=9230",
|
||||
"lint": "ts-node ./i18n/schema-validation.ts",
|
||||
@@ -159,6 +160,7 @@
|
||||
"core-js": "2.6.12",
|
||||
"dotenv": "16.4.5",
|
||||
"gatsby-plugin-webpack-bundle-analyser-v2": "1.1.32",
|
||||
"i18next-fs-backend": "2.3.2",
|
||||
"jest-json-schema-extended": "1.0.1",
|
||||
"joi": "17.12.2",
|
||||
"js-yaml": "4.1.0",
|
||||
|
||||
@@ -9,7 +9,12 @@ const SearchBarOptimized = ({
|
||||
innerRef
|
||||
}: Pick<SearchBarProps, 'innerRef'>): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const placeholder = t('search.placeholder');
|
||||
// TODO: Refactor this fallback when all translation files are synced
|
||||
const searchPlaceholder = t('search-bar:placeholder').startsWith(
|
||||
'search.placeholder.'
|
||||
)
|
||||
? t('search.placeholder')
|
||||
: t('search-bar:placeholder');
|
||||
const searchUrl = searchPageUrl;
|
||||
const [value, setValue] = useState('');
|
||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
||||
@@ -50,7 +55,7 @@ const SearchBarOptimized = ({
|
||||
className='ais-SearchBox-input'
|
||||
maxLength={512}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
placeholder={searchPlaceholder}
|
||||
spellCheck='false'
|
||||
type='search'
|
||||
value={value}
|
||||
|
||||
@@ -201,6 +201,12 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
|
||||
render(): JSX.Element {
|
||||
const { isDropdownEnabled, isSearchFocused, innerRef, t } = this.props;
|
||||
const { index } = this.state;
|
||||
// TODO: Refactor this fallback when all translation files are synced
|
||||
const searchPlaceholder = t('search-bar:placeholder').startsWith(
|
||||
'search.placeholder.'
|
||||
)
|
||||
? t('search.placeholder')
|
||||
: t('search-bar:placeholder');
|
||||
|
||||
return (
|
||||
<WithInstantSearch>
|
||||
@@ -223,7 +229,7 @@ export class SearchBar extends Component<SearchBarProps, SearchBarState> {
|
||||
translations={{
|
||||
submitTitle: t('icons.magnifier'),
|
||||
resetTitle: t('icons.input-reset'),
|
||||
placeholder: t('search.placeholder')
|
||||
placeholder: searchPlaceholder
|
||||
}}
|
||||
onFocus={this.handleFocus}
|
||||
/>
|
||||
|
||||
175
client/tools/generate-search-placeholder.test.ts
Normal file
175
client/tools/generate-search-placeholder.test.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { clientLocale } from '../config/env.json';
|
||||
import {
|
||||
convertToLocalizedString,
|
||||
generateSearchPlaceholder,
|
||||
roundDownToNearestHundred
|
||||
} from './generate-search-placeholder';
|
||||
|
||||
describe('Search bar placeholder tests:', () => {
|
||||
describe('Number rounding', () => {
|
||||
test('Numbers less than 100 return 0', () => {
|
||||
const testArr = [0, 1, 50, 99];
|
||||
|
||||
testArr.forEach(num => {
|
||||
expect(roundDownToNearestHundred(num)).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('Numbers greater than 100 return a number rounded down to the nearest 100', () => {
|
||||
const testArr = [
|
||||
{
|
||||
num: 100,
|
||||
expected: 100
|
||||
},
|
||||
{
|
||||
num: 101,
|
||||
expected: 100
|
||||
},
|
||||
{
|
||||
num: 199,
|
||||
expected: 100
|
||||
},
|
||||
{
|
||||
num: 999,
|
||||
expected: 900
|
||||
},
|
||||
{
|
||||
num: 1000,
|
||||
expected: 1000
|
||||
},
|
||||
{
|
||||
num: 1001,
|
||||
expected: 1000
|
||||
},
|
||||
{
|
||||
num: 1999,
|
||||
expected: 1900
|
||||
},
|
||||
{
|
||||
num: 10000,
|
||||
expected: 10000
|
||||
},
|
||||
{
|
||||
num: 10001,
|
||||
expected: 10000
|
||||
},
|
||||
{
|
||||
num: 19999,
|
||||
expected: 19900
|
||||
}
|
||||
];
|
||||
|
||||
testArr.forEach(obj => {
|
||||
expect(roundDownToNearestHundred(obj.num)).toEqual(obj.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Number formatting', () => {
|
||||
test('Numbers are converted to the correct decimal or comma format for each locale', () => {
|
||||
const testArr = [
|
||||
{
|
||||
num: 100,
|
||||
locale: 'en',
|
||||
expected: '100'
|
||||
},
|
||||
{
|
||||
num: 100,
|
||||
locale: 'zh',
|
||||
expected: '100'
|
||||
},
|
||||
{
|
||||
num: 100,
|
||||
locale: 'de',
|
||||
expected: '100'
|
||||
},
|
||||
{
|
||||
num: 1000,
|
||||
locale: 'en',
|
||||
expected: '1,000'
|
||||
},
|
||||
{
|
||||
num: 1000,
|
||||
locale: 'zh',
|
||||
expected: '1,000'
|
||||
},
|
||||
{
|
||||
num: 1000,
|
||||
locale: 'de',
|
||||
expected: '1.000'
|
||||
},
|
||||
{
|
||||
num: 10000,
|
||||
locale: 'en',
|
||||
expected: '10,000'
|
||||
},
|
||||
{
|
||||
num: 10000,
|
||||
locale: 'zh',
|
||||
expected: '10,000'
|
||||
},
|
||||
{
|
||||
num: 10000,
|
||||
locale: 'de',
|
||||
expected: '10.000'
|
||||
},
|
||||
{
|
||||
num: 100000,
|
||||
locale: 'en',
|
||||
expected: '100,000'
|
||||
},
|
||||
{
|
||||
num: 100000,
|
||||
locale: 'zh',
|
||||
expected: '100,000'
|
||||
},
|
||||
{
|
||||
num: 100000,
|
||||
locale: 'de',
|
||||
expected: '100.000'
|
||||
}
|
||||
];
|
||||
|
||||
testArr.forEach(obj => {
|
||||
const { num, locale, expected } = obj;
|
||||
expect(convertToLocalizedString(num, locale)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Note: Only test the English locale to prevent duplicate tests,
|
||||
// and just to ensure the logic is working as expected.
|
||||
if (clientLocale === 'english') {
|
||||
describe('Placeholder strings', () => {
|
||||
test('When the total number of hits is less than 100 the expected placeholder is generated', async () => {
|
||||
const expected = 'Search our tutorials';
|
||||
const placeholderText = await generateSearchPlaceholder({
|
||||
mockRecordsNum: 99,
|
||||
locale: 'english'
|
||||
});
|
||||
|
||||
expect(placeholderText).toEqual(expected);
|
||||
});
|
||||
|
||||
test('When the total number of hits is equal to 100 the expected placeholder is generated', async () => {
|
||||
const placeholderText = await generateSearchPlaceholder({
|
||||
mockRecordsNum: 100,
|
||||
locale: 'english'
|
||||
});
|
||||
const expected = 'Search 100+ tutorials';
|
||||
|
||||
expect(placeholderText).toEqual(expected);
|
||||
});
|
||||
|
||||
test('When the total number of hits is greater than 100 the expected placeholder is generated', async () => {
|
||||
const placeholderText = await generateSearchPlaceholder({
|
||||
mockRecordsNum: 11000,
|
||||
locale: 'english'
|
||||
});
|
||||
const expected = 'Search 11,000+ tutorials';
|
||||
|
||||
expect(placeholderText).toEqual(expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
115
client/tools/generate-search-placeholder.ts
Normal file
115
client/tools/generate-search-placeholder.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { writeFileSync, readdirSync, lstatSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import algoliasearch from 'algoliasearch';
|
||||
import i18n from 'i18next';
|
||||
import backend from 'i18next-fs-backend';
|
||||
|
||||
import {
|
||||
algoliaAppId,
|
||||
algoliaAPIKey,
|
||||
clientLocale,
|
||||
environment
|
||||
} from '../config/env.json';
|
||||
import { newsIndex } from '../src/utils/algolia-locale-setup';
|
||||
import { i18nextCodes } from '../../shared/config/i18n';
|
||||
|
||||
const i18nextCode = i18nextCodes[clientLocale as keyof typeof i18nextCodes];
|
||||
|
||||
i18n
|
||||
.use(backend)
|
||||
.init({
|
||||
defaultNS: 'translations',
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
initImmediate: false,
|
||||
preload: readdirSync(join(__dirname, '../i18n/locales')).filter(
|
||||
fileName => {
|
||||
const joinedPath = join(join(__dirname, '../i18n/locales'), fileName);
|
||||
const isDirectory = lstatSync(joinedPath).isDirectory();
|
||||
return isDirectory;
|
||||
}
|
||||
),
|
||||
lng: i18nextCode,
|
||||
ns: ['translations'],
|
||||
backend: {
|
||||
loadPath: resolve(
|
||||
__dirname,
|
||||
`../i18n/locales/${clientLocale}/translations.json`
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
throw Error(error.message);
|
||||
});
|
||||
|
||||
const t = i18n.t.bind(i18n);
|
||||
|
||||
export const roundDownToNearestHundred = (num: number) =>
|
||||
Math.floor(num / 100) * 100;
|
||||
|
||||
export const convertToLocalizedString = (num: number, ISOCode: string) =>
|
||||
num.toLocaleString(ISOCode);
|
||||
|
||||
interface GenerateSearchPlaceholderOptions {
|
||||
locale?: string;
|
||||
mockRecordsNum?: number;
|
||||
}
|
||||
|
||||
export const generateSearchPlaceholder = async (
|
||||
options: GenerateSearchPlaceholderOptions = {}
|
||||
) => {
|
||||
const { locale, mockRecordsNum } = options;
|
||||
let placeholderText = t('search.placeholder.default');
|
||||
|
||||
try {
|
||||
let totalRecords = mockRecordsNum || 0;
|
||||
if (!mockRecordsNum) {
|
||||
const algoliaClient = algoliasearch(algoliaAppId, algoliaAPIKey);
|
||||
const index = algoliaClient.initIndex(newsIndex);
|
||||
const res = await index.search('');
|
||||
totalRecords = res.nbHits;
|
||||
}
|
||||
const roundedTotalRecords = roundDownToNearestHundred(totalRecords);
|
||||
|
||||
if (roundedTotalRecords >= 100) {
|
||||
placeholderText = i18n.t('search.placeholder.numbered', {
|
||||
roundedTotalRecords: convertToLocalizedString(
|
||||
roundedTotalRecords,
|
||||
i18nextCode
|
||||
)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (environment === 'production') {
|
||||
console.warn(`
|
||||
----------------------------------------------------------
|
||||
Warning: Could not get the total number of Algolia records
|
||||
----------------------------------------------------------
|
||||
Make sure that Algolia keys and index are set up correctly.
|
||||
|
||||
Falling back to the default search placeholder text.
|
||||
----------------------------------------------------------
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
resolve(
|
||||
__dirname,
|
||||
`../i18n/locales/${locale ? locale : clientLocale}/search-bar.json`
|
||||
),
|
||||
JSON.stringify({
|
||||
placeholder: placeholderText
|
||||
})
|
||||
);
|
||||
|
||||
return placeholderText; // for testing
|
||||
};
|
||||
|
||||
void generateSearchPlaceholder();
|
||||
// TODO: remove the need to fallback to english once we're confident it's
|
||||
// unnecessary (client/i18n/config.js will need all references to 'en' removing)
|
||||
if (clientLocale !== 'english')
|
||||
void generateSearchPlaceholder({ locale: 'english' });
|
||||
@@ -28,9 +28,11 @@ test.describe('Search bar optimized', () => {
|
||||
const searchInput = await getSearchInput({ page, isMobile });
|
||||
|
||||
await expect(searchInput).toBeVisible();
|
||||
// Because we're mocking Algolia requests, the placeholder
|
||||
// should be the default one.
|
||||
await expect(searchInput).toHaveAttribute(
|
||||
'placeholder',
|
||||
translations.search.placeholder
|
||||
translations.search.placeholder.default
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -73,9 +73,11 @@ test.describe('Search bar', () => {
|
||||
const searchInput = await getSearchInput({ page, isMobile });
|
||||
|
||||
await expect(searchInput).toBeVisible();
|
||||
// Because we're mocking Algolia requests, the placeholder
|
||||
// should be the default one.
|
||||
await expect(searchInput).toHaveAttribute(
|
||||
'placeholder',
|
||||
translations.search.placeholder
|
||||
translations.search.placeholder.default
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Submit search terms' })
|
||||
|
||||
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@@ -795,6 +795,9 @@ importers:
|
||||
gatsby-plugin-webpack-bundle-analyser-v2:
|
||||
specifier: 1.1.32
|
||||
version: 1.1.32(gatsby@3.15.0(@types/node@20.12.8)(babel-eslint@10.1.0(eslint@7.32.0))(eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@7.1.1(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint-plugin-testing-library@3.9.0(eslint@7.32.0)(typescript@5.2.2))(react-dom@16.14.0(react@16.14.0))(react@16.14.0)(typescript@5.2.2))
|
||||
i18next-fs-backend:
|
||||
specifier: 2.3.2
|
||||
version: 2.3.2
|
||||
jest-json-schema-extended:
|
||||
specifier: 1.0.1
|
||||
version: 1.0.1
|
||||
@@ -1125,13 +1128,13 @@ importers:
|
||||
version: 4.17.12
|
||||
babel-loader:
|
||||
specifier: 8.3.0
|
||||
version: 8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)))
|
||||
version: 8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0))
|
||||
chai:
|
||||
specifier: 4.4.1
|
||||
version: 4.4.1
|
||||
copy-webpack-plugin:
|
||||
specifier: 9.1.0
|
||||
version: 9.1.0(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)))
|
||||
version: 9.1.0(webpack@5.90.3(webpack-cli@4.10.0))
|
||||
enzyme:
|
||||
specifier: 3.11.0
|
||||
version: 3.11.0
|
||||
@@ -1158,7 +1161,7 @@ importers:
|
||||
version: 0.12.5
|
||||
webpack:
|
||||
specifier: 5.90.3
|
||||
version: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
version: 5.90.3(webpack-cli@4.10.0)
|
||||
webpack-cli:
|
||||
specifier: 4.10.0
|
||||
version: 4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)
|
||||
@@ -8122,6 +8125,9 @@ packages:
|
||||
hyphenate-style-name@1.0.4:
|
||||
resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==}
|
||||
|
||||
i18next-fs-backend@2.3.2:
|
||||
resolution: {integrity: sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==}
|
||||
|
||||
i18next@22.5.1:
|
||||
resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==}
|
||||
|
||||
@@ -18258,7 +18264,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 20.8.0
|
||||
tapable: 2.2.1
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- esbuild
|
||||
@@ -19000,9 +19006,9 @@ snapshots:
|
||||
'@webassemblyjs/ast': 1.11.6
|
||||
'@xtuc/long': 4.2.2
|
||||
|
||||
'@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)))':
|
||||
'@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0))':
|
||||
dependencies:
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
webpack-cli: 4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)
|
||||
|
||||
'@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))':
|
||||
@@ -19501,14 +19507,14 @@ snapshots:
|
||||
schema-utils: 2.7.1
|
||||
webpack: 5.90.3
|
||||
|
||||
babel-loader@8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))):
|
||||
babel-loader@8.3.0(@babel/core@7.23.7)(webpack@5.90.3(webpack-cli@4.10.0)):
|
||||
dependencies:
|
||||
'@babel/core': 7.23.7
|
||||
find-cache-dir: 3.3.2
|
||||
loader-utils: 2.0.4
|
||||
make-dir: 3.1.0
|
||||
schema-utils: 2.7.1
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
|
||||
babel-plugin-add-module-exports@1.0.4: {}
|
||||
|
||||
@@ -20626,7 +20632,7 @@ snapshots:
|
||||
|
||||
copy-descriptor@0.1.1: {}
|
||||
|
||||
copy-webpack-plugin@9.1.0(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))):
|
||||
copy-webpack-plugin@9.1.0(webpack@5.90.3(webpack-cli@4.10.0)):
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
glob-parent: 6.0.2
|
||||
@@ -20634,7 +20640,7 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
schema-utils: 3.3.0
|
||||
serialize-javascript: 6.0.1
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
|
||||
core-js-compat@3.33.0:
|
||||
dependencies:
|
||||
@@ -23908,6 +23914,8 @@ snapshots:
|
||||
|
||||
hyphenate-style-name@1.0.4: {}
|
||||
|
||||
i18next-fs-backend@2.3.2: {}
|
||||
|
||||
i18next@22.5.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.9
|
||||
@@ -29426,14 +29434,14 @@ snapshots:
|
||||
|
||||
term-size@2.2.1: {}
|
||||
|
||||
terser-webpack-plugin@5.3.10(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))):
|
||||
terser-webpack-plugin@5.3.10(webpack@5.90.3(webpack-cli@4.10.0)):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.3.0
|
||||
serialize-javascript: 6.0.1
|
||||
terser: 5.28.1
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
|
||||
terser-webpack-plugin@5.3.10(webpack@5.90.3):
|
||||
dependencies:
|
||||
@@ -30308,7 +30316,7 @@ snapshots:
|
||||
webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3):
|
||||
dependencies:
|
||||
'@discoveryjs/json-ext': 0.5.7
|
||||
'@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)))
|
||||
'@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))(webpack@5.90.3(webpack-cli@4.10.0))
|
||||
'@webpack-cli/info': 1.5.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
'@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
colorette: 2.0.20
|
||||
@@ -30318,7 +30326,7 @@ snapshots:
|
||||
import-local: 3.1.0
|
||||
interpret: 2.2.0
|
||||
rechoir: 0.7.1
|
||||
webpack: 5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3))
|
||||
webpack: 5.90.3(webpack-cli@4.10.0)
|
||||
webpack-merge: 5.9.0
|
||||
optionalDependencies:
|
||||
webpack-bundle-analyzer: 4.10.1
|
||||
@@ -30384,7 +30392,7 @@ snapshots:
|
||||
- esbuild
|
||||
- uglify-js
|
||||
|
||||
webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)):
|
||||
webpack@5.90.3(webpack-cli@4.10.0):
|
||||
dependencies:
|
||||
'@types/eslint-scope': 3.7.5
|
||||
'@types/estree': 1.0.5
|
||||
@@ -30407,7 +30415,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.10(webpack@5.90.3(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.1)(webpack@5.90.3)))
|
||||
terser-webpack-plugin: 5.3.10(webpack@5.90.3(webpack-cli@4.10.0))
|
||||
watchpack: 2.4.0
|
||||
webpack-sources: 3.2.3
|
||||
optionalDependencies:
|
||||
|
||||
Reference in New Issue
Block a user