From e103bf923f87080e3a17755c03a2ade41d75fd18 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 1 Nov 2024 15:31:17 -0700 Subject: [PATCH] Move all internal modules in the kolibri package into folders called 'internal'. Update the kolibri package build tools to leverage this fact to automatically generate the exports field. --- Makefile | 2 +- .../kolibri-tools/lib/apiSpecExportTools.js | 59 +++++-- packages/kolibri/README.md | 3 + packages/kolibri/__tests__/heartbeat.spec.js | 2 +- packages/kolibri/client.js | 2 +- .../ContentRenderer/ContentRendererError.vue | 0 .../ContentRendererLoading.vue | 0 .../__tests__/ContentRendererError.spec.js | 0 .../__tests__/ContentRendererLoading.spec.js | 0 .../{ => internal}/ContentRenderer/index.js | 0 .../{ => internal}/ContentRenderer/mixin.js | 0 .../LanguageSwitcherList.vue | 6 +- .../LanguageSwitcherModal.vue | 4 +- .../{ => internal}/SelectedLanguage.vue | 0 .../{ => internal}/language-names.scss | 0 .../language-switcher/{ => internal}/mixin.js | 0 .../components/pages/AppBarPage/index.vue | 4 +- .../AppBarPage/{ => internal}/AppBar.vue | 2 +- .../{ => internal}/BottomNavigationBar.vue | 0 .../{ => internal}/LearnOnlyDeviceNotice.vue | 0 .../{ => internal}/Navbar/NavbarLink.vue | 0 .../Navbar/__tests__/nav-bar.spec.js | 0 .../{ => internal}/Navbar/index.vue | 0 .../AppBarPage/{ => internal}/SideNav.vue | 3 +- .../{ => internal}/SideNavDivider.vue | 0 .../AppBarPage/{ => internal}/TotalPoints.vue | 0 .../__tests__/TotalPoints.spec.js | 0 .../{ => internal}/__tests__/app-bar.spec.js | 0 .../{ => internal}/__tests__/side-nav.spec.js | 2 +- .../components/pages/ImmersivePage/index.vue | 2 +- .../{ => internal}/ImmersiveToolbar.vue | 0 .../pages/NotificationsRoot/index.vue | 2 +- .../{ => internal}/UpdateNotification.vue | 0 .../__tests__/generateNavRoutes.spec.js | 0 .../__tests__/useScrollPosition.spec.js | 0 .../{ => internal}/generateNavRoutes.js | 0 .../{ => internal}/useScrollPosition.js | 0 packages/kolibri/composables/useNav.js | 2 +- packages/kolibri/heartbeat.js | 4 +- packages/kolibri/index.js | 8 +- .../__tests__/pluginMediator.spec.js | 0 packages/kolibri/{ => internal}/apiSpec.js | 6 +- .../kolibri/{ => internal}/disconnection.js | 0 .../kolibri/{ => internal}/pluginMediator.js | 6 +- .../kolibri/{ => internal}/useConnection.js | 0 packages/kolibri/package.json | 152 +++++++++--------- .../styles/{ => internal}/initializeTheme.js | 0 .../kolibri/styles/{ => internal}/main.scss | 0 .../styles/{ => internal}/themeSpec.js | 0 packages/kolibri/utils/i18n.js | 6 +- .../utils/{ => internal}/intl-locale-data.js | 0 .../utils/{ => internal}/setupAndLoadFonts.js | 0 .../{ => internal}/vue-intl-locale-data.js | 0 53 files changed, 158 insertions(+), 119 deletions(-) create mode 100644 packages/kolibri/README.md rename packages/kolibri/components/{ => internal}/ContentRenderer/ContentRendererError.vue (100%) rename packages/kolibri/components/{ => internal}/ContentRenderer/ContentRendererLoading.vue (100%) rename packages/kolibri/components/{ => internal}/ContentRenderer/__tests__/ContentRendererError.spec.js (100%) rename packages/kolibri/components/{ => internal}/ContentRenderer/__tests__/ContentRendererLoading.spec.js (100%) rename packages/kolibri/components/{ => internal}/ContentRenderer/index.js (100%) rename packages/kolibri/components/{ => internal}/ContentRenderer/mixin.js (100%) rename packages/kolibri/components/language-switcher/{ => internal}/SelectedLanguage.vue (100%) rename packages/kolibri/components/language-switcher/{ => internal}/language-names.scss (100%) rename packages/kolibri/components/language-switcher/{ => internal}/mixin.js (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/AppBar.vue (99%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/BottomNavigationBar.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/LearnOnlyDeviceNotice.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/Navbar/NavbarLink.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/Navbar/__tests__/nav-bar.spec.js (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/Navbar/index.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/SideNav.vue (99%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/SideNavDivider.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/TotalPoints.vue (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/__tests__/TotalPoints.spec.js (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/__tests__/app-bar.spec.js (100%) rename packages/kolibri/components/pages/AppBarPage/{ => internal}/__tests__/side-nav.spec.js (99%) rename packages/kolibri/components/pages/ImmersivePage/{ => internal}/ImmersiveToolbar.vue (100%) rename packages/kolibri/components/pages/NotificationsRoot/{ => internal}/UpdateNotification.vue (100%) rename packages/kolibri/composables/{ => internal}/__tests__/generateNavRoutes.spec.js (100%) rename packages/kolibri/composables/{ => internal}/__tests__/useScrollPosition.spec.js (100%) rename packages/kolibri/composables/{ => internal}/generateNavRoutes.js (100%) rename packages/kolibri/composables/{ => internal}/useScrollPosition.js (100%) rename packages/kolibri/{ => internal}/__tests__/pluginMediator.spec.js (100%) rename packages/kolibri/{ => internal}/apiSpec.js (94%) rename packages/kolibri/{ => internal}/disconnection.js (100%) rename packages/kolibri/{ => internal}/pluginMediator.js (98%) rename packages/kolibri/{ => internal}/useConnection.js (100%) rename packages/kolibri/styles/{ => internal}/initializeTheme.js (100%) rename packages/kolibri/styles/{ => internal}/main.scss (100%) rename packages/kolibri/styles/{ => internal}/themeSpec.js (100%) rename packages/kolibri/utils/{ => internal}/intl-locale-data.js (100%) rename packages/kolibri/utils/{ => internal}/setupAndLoadFonts.js (100%) rename packages/kolibri/utils/{ => internal}/vue-intl-locale-data.js (100%) diff --git a/Makefile b/Makefile index 3f11bc81650..8a25863424d 100644 --- a/Makefile +++ b/Makefile @@ -231,7 +231,7 @@ i18n-pretranslate-approve-all: i18n-download-translations: python packages/kolibri-tools/lib/i18n/crowdin.py rebuild-translations ${branch} python packages/kolibri-tools/lib/i18n/crowdin.py download-translations ${branch} - yarn exec kolibri-tools i18n-code-gen -- --output-dir ./packages/kolibri/utils + yarn exec kolibri-tools i18n-code-gen -- --output-dir ./packages/kolibri/utils/internal $(MAKE) i18n-django-compilemessages yarn exec kolibri-tools i18n-create-message-files -- --pluginFile ./build_tools/build_plugins.txt diff --git a/packages/kolibri-tools/lib/apiSpecExportTools.js b/packages/kolibri-tools/lib/apiSpecExportTools.js index 56a4480ae1e..fb6c7ed8320 100644 --- a/packages/kolibri-tools/lib/apiSpecExportTools.js +++ b/packages/kolibri-tools/lib/apiSpecExportTools.js @@ -1,22 +1,29 @@ const path = require('node:path'); const kolibriPackageJson = require('../../kolibri/package.json'); const writeSourceToFile = require('./i18n/writeSourceToFile'); +const glob = require('./glob'); const apiSpec = kolibriPackageJson.exports || {}; const { kolibriName } = require('./kolibriName'); -// Generate a list of all the module imports that we need to expose -// Iterate over all the exports in the kolibri package -const apiKeys = Object.keys(apiSpec) - // Filter out the export for the root package '.' as we don't need to expose that - .filter(key => key !== '.') - // Add the kolibri prefix and remove the leading '.' to make a full import path - // e.g. './urls' -> 'kolibri/urls' - .map(key => 'kolibri' + key.slice(1)) - // Add the list of modules that are exposed in the kolibri package.json - // Unmodified, as they are already full import paths, e.g. 'vue' - .concat(kolibriPackageJson.exposes); +function generateApiKeys(apiSpec) { + // Generate a list of all the module imports that we need to expose + // Iterate over all the exports in the kolibri package + return ( + Object.keys(apiSpec) + // Filter out the export for the root package '.' as we don't need to expose that + .filter(key => key !== '.') + // Add the kolibri prefix and remove the leading '.' to make a full import path + // e.g. './urls' -> 'kolibri/urls' + .map(key => 'kolibri' + key.slice(1)) + // Add the list of modules that are exposed in the kolibri package.json + // Unmodified, as they are already full import paths, e.g. 'vue' + .concat(kolibriPackageJson.exposes) + ); +} + +const apiKeys = generateApiKeys(apiSpec); const coreExternals = { // The kolibri package itself is a special case, as it is the root of the package @@ -38,10 +45,36 @@ const apiSpecHeader = ` `; function rebuildApiSpec() { - const apiSpecFilePath = path.resolve(__dirname, '../../kolibri/apiSpec.js'); + // First we read the directory structure of the kolibri folder to infer the list of modules + // that are available to be imported. + const kolibriFolder = path.resolve(__dirname, '../../kolibri'); + const kolibriFiles = glob + .sync(`${kolibriFolder}/**/*.{js,vue}`, { + ignore: ['**/internal/**', '**/__tests__/**', '**/__mocks__/**'], + }) + .map(f => f.split('.')[0]) + .map(f => f.replace(kolibriFolder, '')) + .map(f => f.replace(/\/index$/, '')) + .sort(); + // Then we generate the list of modules that are exposed in the kolibri package.json + const newApiSpec = {}; + for (const key of kolibriFiles) { + const specValue = '.' + key; + // When we are able to move everything into a src folder + // we can update the right hand side of this + newApiSpec[specValue] = specValue === '.' ? './index' : specValue; + } + const updatedKolibriPackageJson = { + ...kolibriPackageJson, + exports: newApiSpec, + }; + const kolibriPackageJsonFilePath = path.resolve(__dirname, '../../kolibri/package.json'); + writeSourceToFile(kolibriPackageJsonFilePath, JSON.stringify(updatedKolibriPackageJson, null, 2)); + const apiSpecFilePath = path.resolve(__dirname, '../../kolibri/internal/apiSpec.js'); + const updatedApiKeys = generateApiKeys(updatedKolibriPackageJson.exports); let apiSpecContent = apiSpecHeader; apiSpecContent += 'export default {\n'; - for (const key of apiKeys) { + for (const key of updatedApiKeys) { apiSpecContent += ` '${key}': require('${key}'),\n`; } apiSpecContent += '};\n'; diff --git a/packages/kolibri/README.md b/packages/kolibri/README.md new file mode 100644 index 00000000000..b807d73cb03 --- /dev/null +++ b/packages/kolibri/README.md @@ -0,0 +1,3 @@ +This is the kolibri JS public API. All the exports and 'exposes' fields in the package.json are available at runtime as part of the Kolibri core bundle, so JS built with the Kolibri webpack configuration will defer to a global object to read these modules at runtime. + +Only the files detailed in 'exports' are available for import from this package, as per ESM conventions - to further clarify which modules are importable, all the code that is only used internally within this package is included inside folders called 'internal'. Any code inside these folders will not be directly importable. diff --git a/packages/kolibri/__tests__/heartbeat.spec.js b/packages/kolibri/__tests__/heartbeat.spec.js index ce1ee648d3d..1e2a286d569 100644 --- a/packages/kolibri/__tests__/heartbeat.spec.js +++ b/packages/kolibri/__tests__/heartbeat.spec.js @@ -7,7 +7,7 @@ import useSnackbar, { useSnackbarMock } from 'kolibri/composables/useSnackbar'; import { ref } from '@vue/composition-api'; import { DisconnectionErrorCodes } from 'kolibri/constants'; import { HeartBeat } from '../heartbeat.js'; -import { trs } from '../disconnection'; +import { trs } from '../internal/disconnection'; import coreModule from '../../../kolibri/core/assets/src/state/modules/core'; import { stubWindowLocation } from 'testUtils'; // eslint-disable-line diff --git a/packages/kolibri/client.js b/packages/kolibri/client.js index 8ba6f0db888..5a319f39175 100644 --- a/packages/kolibri/client.js +++ b/packages/kolibri/client.js @@ -10,7 +10,7 @@ import { get } from '@vueuse/core'; import useUser from 'kolibri/composables/useUser'; import { DisconnectionErrorCodes } from 'kolibri/constants'; import clientFactory from 'kolibri/utils/baseClient'; -import useConnection from './useConnection'; +import useConnection from './internal/useConnection'; export const logging = logger.getLogger(__filename); diff --git a/packages/kolibri/components/ContentRenderer/ContentRendererError.vue b/packages/kolibri/components/internal/ContentRenderer/ContentRendererError.vue similarity index 100% rename from packages/kolibri/components/ContentRenderer/ContentRendererError.vue rename to packages/kolibri/components/internal/ContentRenderer/ContentRendererError.vue diff --git a/packages/kolibri/components/ContentRenderer/ContentRendererLoading.vue b/packages/kolibri/components/internal/ContentRenderer/ContentRendererLoading.vue similarity index 100% rename from packages/kolibri/components/ContentRenderer/ContentRendererLoading.vue rename to packages/kolibri/components/internal/ContentRenderer/ContentRendererLoading.vue diff --git a/packages/kolibri/components/ContentRenderer/__tests__/ContentRendererError.spec.js b/packages/kolibri/components/internal/ContentRenderer/__tests__/ContentRendererError.spec.js similarity index 100% rename from packages/kolibri/components/ContentRenderer/__tests__/ContentRendererError.spec.js rename to packages/kolibri/components/internal/ContentRenderer/__tests__/ContentRendererError.spec.js diff --git a/packages/kolibri/components/ContentRenderer/__tests__/ContentRendererLoading.spec.js b/packages/kolibri/components/internal/ContentRenderer/__tests__/ContentRendererLoading.spec.js similarity index 100% rename from packages/kolibri/components/ContentRenderer/__tests__/ContentRendererLoading.spec.js rename to packages/kolibri/components/internal/ContentRenderer/__tests__/ContentRendererLoading.spec.js diff --git a/packages/kolibri/components/ContentRenderer/index.js b/packages/kolibri/components/internal/ContentRenderer/index.js similarity index 100% rename from packages/kolibri/components/ContentRenderer/index.js rename to packages/kolibri/components/internal/ContentRenderer/index.js diff --git a/packages/kolibri/components/ContentRenderer/mixin.js b/packages/kolibri/components/internal/ContentRenderer/mixin.js similarity index 100% rename from packages/kolibri/components/ContentRenderer/mixin.js rename to packages/kolibri/components/internal/ContentRenderer/mixin.js diff --git a/packages/kolibri/components/language-switcher/LanguageSwitcherList.vue b/packages/kolibri/components/language-switcher/LanguageSwitcherList.vue index 16cb4d931b8..b46f896ddb2 100644 --- a/packages/kolibri/components/language-switcher/LanguageSwitcherList.vue +++ b/packages/kolibri/components/language-switcher/LanguageSwitcherList.vue @@ -55,8 +55,8 @@ import { availableLanguages, compareLanguages, currentLanguage } from 'kolibri/utils/i18n'; import LanguageSwitcherModal from 'kolibri/components/language-switcher/LanguageSwitcherModal'; - import languageSwitcherMixin from './mixin'; - import SelectedLanguage from './SelectedLanguage'; + import languageSwitcherMixin from './internal/mixin'; + import SelectedLanguage from './internal/SelectedLanguage'; const prioritizedLanguages = ['en', 'ar', 'es-419', 'hi-in', 'fr-fr', 'sw-tz']; @@ -118,7 +118,7 @@