Skip to content

Commit

Permalink
Move all internal modules in the kolibri package into folders called …
Browse files Browse the repository at this point in the history
…'internal'.

Update the kolibri package build tools to leverage this fact to automatically generate the exports field.
  • Loading branch information
rtibbles committed Nov 1, 2024
1 parent 18fec73 commit 687990c
Show file tree
Hide file tree
Showing 53 changed files with 158 additions and 118 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
59 changes: 46 additions & 13 deletions packages/kolibri-tools/lib/apiSpecExportTools.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions packages/kolibri/README.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/kolibri/__tests__/heartbeat.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion packages/kolibri/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -118,7 +118,7 @@

<style lang="scss" scoped>
@import './language-names';
@import './internal/language-names';
.globe {
position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
import commonCoreStrings from 'kolibri/uiText/commonCoreStrings';
import FocusTrap from 'kolibri-common/components/FocusTrap';
import languageSwitcherMixin from './mixin';
import languageSwitcherMixin from './internal/mixin';
export default {
name: 'LanguageSwitcherModal',
Expand Down Expand Up @@ -98,7 +98,7 @@

<style lang="scss" scoped>
@import './language-names';
@import './internal/language-names';
.language-name {
@include font-family-language-names;
Expand Down
4 changes: 2 additions & 2 deletions packages/kolibri/components/pages/AppBarPage/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
import useUserSyncStatus from 'kolibri-common/composables/useUserSyncStatus';
import useUser from 'kolibri/composables/useUser';
import ScrollingHeader from '../ScrollingHeader';
import AppBar from './AppBar';
import SideNav from './SideNav';
import AppBar from './internal/AppBar';
import SideNav from './internal/SideNav';
export default {
name: 'AppBarPage',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
import useTotalProgress from 'kolibri/composables/useTotalProgress';
import useNav from 'kolibri/composables/useNav';
import useUser from 'kolibri/composables/useUser';
import SkipNavigationLink from '../../SkipNavigationLink';
import SkipNavigationLink from '../../../SkipNavigationLink';
import Navbar from './Navbar';
const hashedValuePattern = /^[a-f0-9]{30}$/;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
import useUser from 'kolibri/composables/useUser';
import useUserSyncStatus from 'kolibri-common/composables/useUserSyncStatus';
import FocusTrap from 'kolibri-common/components/FocusTrap';
import SyncStatusDisplay from '../../SyncStatusDisplay';
import SyncStatusDisplay from '../../../SyncStatusDisplay';
import LearnOnlyDeviceNotice from './LearnOnlyDeviceNotice';
import TotalPoints from './TotalPoints';
import SideNavDivider from './SideNavDivider';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SideNav from '../SideNav';
// eslint-disable-next-line import/named
// eslint-disable-next-line import/named
import LearnOnlyDeviceNotice from '../LearnOnlyDeviceNotice';
import SyncStatusDisplay from '../../../SyncStatusDisplay';
import SyncStatusDisplay from '../../../../SyncStatusDisplay';
import { stubWindowLocation } from 'testUtils'; // eslint-disable-line

jest.mock('kolibri/urls');
Expand Down
2 changes: 1 addition & 1 deletion packages/kolibri/components/pages/ImmersivePage/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import { mapGetters } from 'vuex';
import ScrollingHeader from '../ScrollingHeader';
import ImmersiveToolbar from './ImmersiveToolbar';
import ImmersiveToolbar from './internal/ImmersiveToolbar';
export default {
name: 'ImmersivePage',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
import AppError from 'kolibri-common/components/AppError';
import GlobalSnackbar from 'kolibri-common/components/GlobalSnackbar';
import useUser from 'kolibri/composables/useUser';
import UpdateNotification from './UpdateNotification';
import UpdateNotification from './internal/UpdateNotification';
export default {
name: 'NotificationsRoot',
Expand Down
2 changes: 1 addition & 1 deletion packages/kolibri/composables/useNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { get } from '@vueuse/core';
import { UserKinds, NavComponentSections } from 'kolibri/constants';
import logger from 'kolibri-logging';
import { computed, getCurrentInstance } from '@vue/composition-api';
import { generateNavRoute } from './generateNavRoutes';
import { generateNavRoute } from './internal/generateNavRoutes';

const logging = logger.getLogger(__filename);

Expand Down
4 changes: 2 additions & 2 deletions packages/kolibri/heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import useUser from 'kolibri/composables/useUser';
import { DisconnectionErrorCodes, SIGNED_OUT_DUE_TO_INACTIVITY } from 'kolibri/constants';
import clientFactory from 'kolibri/utils/baseClient';
import { browser, os } from 'kolibri/utils/browserInfo';
import useConnection from './useConnection';
import useConnection from './internal/useConnection';
import {
createTryingToReconnectSnackbar,
createDisconnectedSnackbar,
createReconnectedSnackbar,
} from './disconnection';
} from './internal/disconnection';

const logging = logger.getLogger(__filename);

Expand Down
8 changes: 4 additions & 4 deletions packages/kolibri/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import VueRouter from 'vue-router';
import Vuex from 'vuex';
import VueCompositionApi from '@vue/composition-api';
import KThemePlugin from 'kolibri-design-system/lib/KThemePlugin';
import ContentRenderer from './components/ContentRenderer';
import initializeTheme from './styles/initializeTheme';
import setupPluginMediator from './pluginMediator';
import apiSpec from './apiSpec';
import ContentRenderer from './components/internal/ContentRenderer';
import initializeTheme from './styles/internal/initializeTheme';
import setupPluginMediator from './internal/pluginMediator';
import apiSpec from './internal/apiSpec';

/**
* Object that forms the public API for the Kolibri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ export default {
'kolibri/components/CoreLogo': require('kolibri/components/CoreLogo'),
'kolibri/components/CoreTable': require('kolibri/components/CoreTable'),
'kolibri/components/FilterTextbox': require('kolibri/components/FilterTextbox'),
'kolibri/components/SkipNavigationLink': require('kolibri/components/SkipNavigationLink'),
'kolibri/components/SyncStatusDisplay': require('kolibri/components/SyncStatusDisplay'),
'kolibri/components/language-switcher/LanguageSwitcherList': require('kolibri/components/language-switcher/LanguageSwitcherList'),
'kolibri/components/language-switcher/LanguageSwitcherModal': require('kolibri/components/language-switcher/LanguageSwitcherModal'),
'kolibri/components/pages/AppBarPage': require('kolibri/components/pages/AppBarPage'),
'kolibri/components/pages/ImmersivePage': require('kolibri/components/pages/ImmersivePage'),
'kolibri/components/pages/NotificationsRoot': require('kolibri/components/pages/NotificationsRoot'),
'kolibri/components/SkipNavigationLink': require('kolibri/components/SkipNavigationLink'),
'kolibri/components/SyncStatusDisplay': require('kolibri/components/SyncStatusDisplay'),
'kolibri/components/pages/ScrollingHeader': require('kolibri/components/pages/ScrollingHeader'),
'kolibri/composables/useMinimumKolibriVersion': require('kolibri/composables/useMinimumKolibriVersion'),
'kolibri/composables/useNav': require('kolibri/composables/useNav'),
'kolibri/composables/useNow': require('kolibri/composables/useNow'),
Expand All @@ -34,6 +35,7 @@ export default {
'kolibri/uiText/bytesForHumans': require('kolibri/uiText/bytesForHumans'),
'kolibri/uiText/commonCoreStrings': require('kolibri/uiText/commonCoreStrings'),
'kolibri/uiText/licenses': require('kolibri/uiText/licenses'),
'kolibri/uiText/notificationStrings': require('kolibri/uiText/notificationStrings'),
'kolibri/urls': require('kolibri/urls'),
'kolibri/utils/CatchErrors': require('kolibri/utils/CatchErrors'),
'kolibri/utils/appCapabilities': require('kolibri/utils/appCapabilities'),
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Vue from 'vue';
import scriptLoader from 'kolibri-common/utils/scriptLoader';
import { RENDERER_SUFFIX } from 'kolibri/constants';
import { languageDirection, languageDirections } from 'kolibri/utils/i18n';
import contentRendererMixin from './components/ContentRenderer/mixin';
import ContentRendererLoading from './components/ContentRenderer/ContentRendererLoading';
import ContentRendererError from './components/ContentRenderer/ContentRendererError';
import contentRendererMixin from '../components/internal/ContentRenderer/mixin';
import ContentRendererLoading from '../components/internal/ContentRenderer/ContentRendererLoading';
import ContentRendererError from '../components/internal/ContentRenderer/ContentRendererError';

/**
* Array containing the names of all methods of the Mediator that
Expand Down
File renamed without changes.
Loading

0 comments on commit 687990c

Please sign in to comment.