diff --git a/.eslintignore b/.eslintignore index 4dfb13b84..7fdcc7018 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,9 @@ /cli/assets **/locales/index.js -cli/config/init.entrypoint.js -cli/config/init.App.test.js +# These are to avoid lint errors like 'cannot find module App.jsx' +cli/config/templates/init/entrypoint.jsx +cli/config/templates/init/App.test.jsx +cli/config/templates/init-typescript/entrypoint.tsx +cli/config/templates/init-typescript/App.test.tsx +cli/config/templates/init-typescript/global.d.ts +cli/config/templates/init-typescript/modules.d.ts diff --git a/.github/workflows/dhis2-deploy-netlify.yml b/.github/workflows/dhis2-deploy-netlify.yml index 1b8bf10ab..10bf297f0 100644 --- a/.github/workflows/dhis2-deploy-netlify.yml +++ b/.github/workflows/dhis2-deploy-netlify.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 18.x - uses: c-hive/gha-yarn-cache@v1 - run: yarn install --frozen-lockfile diff --git a/.github/workflows/dhis2-verify-lib.yml b/.github/workflows/dhis2-verify-lib.yml index 5cd4020e8..bd351aeda 100644 --- a/.github/workflows/dhis2-verify-lib.yml +++ b/.github/workflows/dhis2-verify-lib.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 18.x - uses: c-hive/gha-yarn-cache@v1 - run: yarn install --frozen-lockfile @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 18.x - uses: c-hive/gha-yarn-cache@v1 - run: yarn install --frozen-lockfile @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 18.x - uses: actions/download-artifact@v4 with: @@ -81,7 +81,7 @@ jobs: token: ${{env.GH_TOKEN}} - uses: actions/setup-node@v1 with: - node-version: 14.x + node-version: 18.x - uses: actions/download-artifact@v4 with: @@ -90,6 +90,10 @@ jobs: - uses: c-hive/gha-yarn-cache@v1 - run: yarn install --frozen-lockfile + # If npm > v7, the semantic release action below modifies yarn.lock + # upon release (since it runs `npm version ___`) + - run: npm install -g npm@^6 + - uses: dhis2/action-semantic-release@master with: publish-npm: true diff --git a/.gitignore b/.gitignore index c7420b8b0..90b115a20 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ cli/assets # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7bcbf76..541dd57e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,100 +1,135 @@ -## [11.7.5](https://github.com/dhis2/app-platform/compare/v11.7.4...v11.7.5) (2024-11-04) +# [12.0.0-alpha.29](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.28...v12.0.0-alpha.29) (2024-12-11) ### Bug Fixes -* **publishVersion:** fix maxBodyLength when uploading to appHub ([c5abfd1](https://github.com/dhis2/app-platform/commit/c5abfd10d2ad04c09797cec2c2abb1a823d93dd9)) +* **deps:** use npm v6 before publishing ([01ad502](https://github.com/dhis2/app-platform/commit/01ad502f039b917e53268fb8b9ec73f14ccf84bc)) -## [11.7.4](https://github.com/dhis2/app-platform/compare/v11.7.3...v11.7.4) (2024-10-22) +# [12.0.0-alpha.28](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.27...v12.0.0-alpha.28) (2024-12-11) ### Bug Fixes -* bump typescript version used in app shell ([8ebb2e8](https://github.com/dhis2/app-platform/commit/8ebb2e8dc46e2747b71226906bf0565eb041bb66)) +* **deps:** upgrade react to 18 in example apps ([#900](https://github.com/dhis2/app-platform/issues/900)) ([7fd16d7](https://github.com/dhis2/app-platform/commit/7fd16d7fa347a804d9c78ae86976859fef54632b)) -## [11.7.3](https://github.com/dhis2/app-platform/compare/v11.7.2...v11.7.3) (2024-10-21) +# [12.0.0-alpha.27](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.26...v12.0.0-alpha.27) (2024-12-10) ### Bug Fixes -* add @babel/plugin-syntax-dynamic-import as a direct dependency ([#886](https://github.com/dhis2/app-platform/issues/886)) ([8c5ef0c](https://github.com/dhis2/app-platform/commit/8c5ef0c394a6cd639daefaae44c786f4919ebd62)) +* **init:** update bootstrap script branch ([#896](https://github.com/dhis2/app-platform/issues/896)) ([33c261a](https://github.com/dhis2/app-platform/commit/33c261a1f33b02b46605b48260987e5d09920227)) +* **plugin:** clean up resize observer and handle sonarqube warnings ([#898](https://github.com/dhis2/app-platform/issues/898)) ([f113dd5](https://github.com/dhis2/app-platform/commit/f113dd5a1ddea58a2f3e94a15c7075a95fee823e)) -## [11.7.2](https://github.com/dhis2/app-platform/compare/v11.7.1...v11.7.2) (2024-09-26) +# [12.0.0-alpha.26](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.25...v12.0.0-alpha.26) (2024-12-06) ### Bug Fixes -* provide fallback api version [LIBS-683] ([#877](https://github.com/dhis2/app-platform/issues/877)) ([dc7bdfa](https://github.com/dhis2/app-platform/commit/dc7bdfab893bdf44ad439e49b25ad3611191ad7a)) +* **deps:** upgrade app-runtime and ui ([#895](https://github.com/dhis2/app-platform/issues/895)) ([8ed0ec3](https://github.com/dhis2/app-platform/commit/8ed0ec3b2e3468d256c7ca5f335c43fcfd040ef6)) + +# [12.0.0-alpha.25](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.24...v12.0.0-alpha.25) (2024-11-21) + + +### Bug Fixes + +* clear only required build dirs ([#894](https://github.com/dhis2/app-platform/issues/894)) ([179305f](https://github.com/dhis2/app-platform/commit/179305ff268a215f0bd0ea1f8edd39f3e3ecbba3)) + +# [12.0.0-alpha.24](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.23...v12.0.0-alpha.24) (2024-11-21) -## [11.7.1](https://github.com/dhis2/app-platform/compare/v11.7.0...v11.7.1) (2024-08-27) + +### Bug Fixes + +* normalize to .js extensions when compiling libraries ([#893](https://github.com/dhis2/app-platform/issues/893)) ([58b33a8](https://github.com/dhis2/app-platform/commit/58b33a812ba22e58a966a535c432dafea4cb8880)) + +# [12.0.0-alpha.23](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.22...v12.0.0-alpha.23) (2024-11-08) ### Bug Fixes +* **service-worker:** handle undefined config vars in injectPrecacheManifest ([a90a4e0](https://github.com/dhis2/app-platform/commit/a90a4e06b25cdecfaf31f24dc51f28c98f20d122)) + +# [12.0.0-alpha.22](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.21...v12.0.0-alpha.22) (2024-11-06) + + +### Bug Fixes + +* **publishVersion:** fix maxBodyLength when uploading to appHub ([c5abfd1](https://github.com/dhis2/app-platform/commit/c5abfd10d2ad04c09797cec2c2abb1a823d93dd9)) +* add @babel/plugin-syntax-dynamic-import as a direct dependency ([#886](https://github.com/dhis2/app-platform/issues/886)) ([8c5ef0c](https://github.com/dhis2/app-platform/commit/8c5ef0c394a6cd639daefaae44c786f4919ebd62)) +* bump typescript version used in app shell ([8ebb2e8](https://github.com/dhis2/app-platform/commit/8ebb2e8dc46e2747b71226906bf0565eb041bb66)) +* provide fallback api version [LIBS-683] ([#877](https://github.com/dhis2/app-platform/issues/877)) ([dc7bdfa](https://github.com/dhis2/app-platform/commit/dc7bdfab893bdf44ad439e49b25ad3611191ad7a)) * update app-runtime dependency ([74a2165](https://github.com/dhis2/app-platform/commit/74a21655bee64c9ca4e6285486b4fabb8bc76959)) -# [11.7.0](https://github.com/dhis2/app-platform/compare/v11.6.4...v11.7.0) (2024-07-23) +## [11.7.5](https://github.com/dhis2/app-platform/compare/v11.7.4...v11.7.5) (2024-11-04) -### Features +### Bug Fixes -* update boilerplate app code for init command [LIBS-644] ([#866](https://github.com/dhis2/app-platform/issues/866)) ([bd6cfc0](https://github.com/dhis2/app-platform/commit/bd6cfc0d132b40c91b44fcdc715befabf2d7e4cf)) +* **publishVersion:** fix maxBodyLength when uploading to appHub ([c5abfd1](https://github.com/dhis2/app-platform/commit/c5abfd10d2ad04c09797cec2c2abb1a823d93dd9)) -## [11.6.4](https://github.com/dhis2/app-platform/compare/v11.6.3...v11.6.4) (2024-07-19) +## [11.7.4](https://github.com/dhis2/app-platform/compare/v11.7.3...v11.7.4) (2024-10-22) ### Bug Fixes -* use i18next-scanner v3 for better i18next compatibility ([#864](https://github.com/dhis2/app-platform/issues/864)) ([84a5a59](https://github.com/dhis2/app-platform/commit/84a5a59747eef82ec85eb602cfef9040efc8fd8a)) +* bump typescript version used in app shell ([8ebb2e8](https://github.com/dhis2/app-platform/commit/8ebb2e8dc46e2747b71226906bf0565eb041bb66)) -## [11.6.3](https://github.com/dhis2/app-platform/compare/v11.6.2...v11.6.3) (2024-07-16) +## [11.7.3](https://github.com/dhis2/app-platform/compare/v11.7.2...v11.7.3) (2024-10-21) ### Bug Fixes -* **deps:** update i18next-scanner version to support old plurals format again ([#861](https://github.com/dhis2/app-platform/issues/861)) ([d0e433b](https://github.com/dhis2/app-platform/commit/d0e433bf0ca213641770e68cf8beb0780570e5c3)) +* add @babel/plugin-syntax-dynamic-import as a direct dependency ([#886](https://github.com/dhis2/app-platform/issues/886)) ([8c5ef0c](https://github.com/dhis2/app-platform/commit/8c5ef0c394a6cd639daefaae44c786f4919ebd62)) -## [11.6.2](https://github.com/dhis2/app-platform/compare/v11.6.1...v11.6.2) (2024-07-10) +## [11.7.2](https://github.com/dhis2/app-platform/compare/v11.7.1...v11.7.2) (2024-09-26) ### Bug Fixes -* plugin boundary retry if plugin logic is skipped ([#862](https://github.com/dhis2/app-platform/issues/862)) ([01a3160](https://github.com/dhis2/app-platform/commit/01a3160242a8352f0bcde229b3e860e396ff5be5)) +* provide fallback api version [LIBS-683] ([#877](https://github.com/dhis2/app-platform/issues/877)) ([dc7bdfa](https://github.com/dhis2/app-platform/commit/dc7bdfab893bdf44ad439e49b25ad3611191ad7a)) -## [11.6.1](https://github.com/dhis2/app-platform/compare/v11.6.0...v11.6.1) (2024-07-05) +## [11.7.1](https://github.com/dhis2/app-platform/compare/v11.7.0...v11.7.1) (2024-08-27) ### Bug Fixes -* **alerts:** ensure hiding works correctly and alerts are not re-added [DHIS2-15438] ([#859](https://github.com/dhis2/app-platform/issues/859)) ([6b11fff](https://github.com/dhis2/app-platform/commit/6b11fff033cc16ed4d2f11caf74e8c18307abc3c)) +* update app-runtime dependency ([74a2165](https://github.com/dhis2/app-platform/commit/74a21655bee64c9ca4e6285486b4fabb8bc76959)) -# [11.6.0](https://github.com/dhis2/app-platform/compare/v11.5.1...v11.6.0) (2024-07-03) +# [11.7.0](https://github.com/dhis2/app-platform/compare/v11.6.4...v11.7.0) (2024-07-23) ### Features -* parse additional namespaces from `d2.config.js` and add to `manifest.webapp` [LIBS-638] ([#860](https://github.com/dhis2/app-platform/issues/860)) ([62782fe](https://github.com/dhis2/app-platform/commit/62782fed6f59a439ab2ef7f00c7e7d88110a78bf)) +* update boilerplate app code for init command [LIBS-644] ([#866](https://github.com/dhis2/app-platform/issues/866)) ([bd6cfc0](https://github.com/dhis2/app-platform/commit/bd6cfc0d132b40c91b44fcdc715befabf2d7e4cf)) -## [11.5.1](https://github.com/dhis2/app-platform/compare/v11.5.0...v11.5.1) (2024-07-01) +# [12.0.0-alpha.3](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.2...v12.0.0-alpha.3) (2024-07-08) ### Bug Fixes +* **alerts:** ensure hiding works correctly and alerts are not re-added [DHIS2-15438] ([#859](https://github.com/dhis2/app-platform/issues/859)) ([6b11fff](https://github.com/dhis2/app-platform/commit/6b11fff033cc16ed4d2f11caf74e8c18307abc3c)) * fixed dimensions plugins [LIBS-634] ([#858](https://github.com/dhis2/app-platform/issues/858)) ([1f717f3](https://github.com/dhis2/app-platform/commit/1f717f391b96a88186c897b9b886e11d6168c87c)) - -# [11.5.0](https://github.com/dhis2/app-platform/compare/v11.4.3...v11.5.0) (2024-06-24) +* small text change in changelog ([824dd2f](https://github.com/dhis2/app-platform/commit/824dd2f877d03a7c4d492dff8999e72421297165)) ### Features * cleanup plugin error boundary [UX-136] ([#856](https://github.com/dhis2/app-platform/issues/856)) ([de252fe](https://github.com/dhis2/app-platform/commit/de252fef91c5037b8b1c238ce25c7e3b90f7c20e)) +* parse additional namespaces from `d2.config.js` and add to `manifest.webapp` [LIBS-638] ([#860](https://github.com/dhis2/app-platform/issues/860)) ([62782fe](https://github.com/dhis2/app-platform/commit/62782fed6f59a439ab2ef7f00c7e7d88110a78bf)) -## [11.4.3](https://github.com/dhis2/app-platform/compare/v11.4.2...v11.4.3) (2024-06-24) +# [12.0.0-alpha.2](https://github.com/dhis2/app-platform/compare/v12.0.0-alpha.1...v12.0.0-alpha.2) (2024-06-20) ### Bug Fixes -* small text change in changelog ([824dd2f](https://github.com/dhis2/app-platform/commit/824dd2f877d03a7c4d492dff8999e72421297165)) +* clean up for plugins [LIBS-620] ([#851](https://github.com/dhis2/app-platform/issues/851)) ([13af3b5](https://github.com/dhis2/app-platform/commit/13af3b5ee862ea4b7952c6a9199505cfe6a1bdaa)) +* do not encode username, password ([#852](https://github.com/dhis2/app-platform/issues/852)) ([2fb4272](https://github.com/dhis2/app-platform/commit/2fb4272130000b383c91d46ba1b3dac44bb13ebf)) +* don't start plugins for apps without a plugin entrypoint ([#850](https://github.com/dhis2/app-platform/issues/850)) ([a89d4cf](https://github.com/dhis2/app-platform/commit/a89d4cf348f7edc0a52b8ab9aacf96f2de939de4)) + + +### Features + +* parse pluginType from d2 config to add to manifest.webapp ([#849](https://github.com/dhis2/app-platform/issues/849)) ([c1dae23](https://github.com/dhis2/app-platform/commit/c1dae238b92183922962811a52ab50d1b73e7995)) +* start plugin and app separately [LIBS-391] [LIBS-392] ([#848](https://github.com/dhis2/app-platform/issues/848)) ([82003e7](https://github.com/dhis2/app-platform/commit/82003e73fce995a83318c623da6028d9975e6686)) ## [11.4.2](https://github.com/dhis2/app-platform/compare/v11.4.1...v11.4.2) (2024-06-18) diff --git a/adapter/package.json b/adapter/package.json index d350e352f..f38733864 100644 --- a/adapter/package.json +++ b/adapter/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/app-adapter", - "version": "11.7.5", + "version": "12.0.0-alpha.29", "repository": { "type": "git", "url": "https://github.com/amcgee/dhis2-app-platform", @@ -21,31 +21,31 @@ "build" ], "dependencies": { - "@dhis2/pwa": "11.7.5", + "@dhis2/pwa": "12.0.0-alpha.29", "moment": "^2.24.0" }, "devDependencies": { - "@dhis2/cli-app-scripts": "11.7.5", - "@testing-library/react": "^12.0.0", - "@testing-library/react-hooks": "^8.0.1", + "@dhis2/cli-app-scripts": "12.0.0-alpha.29", + "@testing-library/react": "^16.0.1", + "@testing-library/dom": "^10.4.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.5", - "react": "^16.8", - "react-dom": "^16.8" + "react": "^18", + "react-dom": "^18" }, "scripts": { "build": "d2-app-scripts build", "test": "d2-app-scripts test" }, "peerDependencies": { - "@dhis2/app-runtime": "^3.10.4", + "@dhis2/app-runtime": "^3.11.1", "@dhis2/d2-i18n": "^1", - "@dhis2/ui": ">=9.8.9", + "@dhis2/ui": ">=10.1.7", "classnames": "^2", "moment": "^2", "prop-types": "^15", - "react": "^16.8", - "react-dom": "^16.8", + "react": "^18", + "react-dom": "^18", "styled-jsx": "^4" }, "jest": { diff --git a/adapter/src/utils/localeUtils.js b/adapter/src/utils/localeUtils.js index aa52f6a9a..6f2d57fb1 100644 --- a/adapter/src/utils/localeUtils.js +++ b/adapter/src/utils/localeUtils.js @@ -132,9 +132,10 @@ export const setMomentLocale = async (locale) => { for (const localeName of localeNameOptions) { try { - await import( - /* webpackChunkName: "moment-locales/[request]" */ `moment/locale/${localeName}` - ) + // Since Vite prefers importing the ESM form of moment, we need + // to import the ESM form of the locales here to use the same + // moment instance + await import(`moment/dist/locale/${localeName}`) moment.locale(localeName) break } catch { diff --git a/adapter/src/utils/useLocale.test.js b/adapter/src/utils/useLocale.test.js index e9bc87cdb..c837eb980 100644 --- a/adapter/src/utils/useLocale.test.js +++ b/adapter/src/utils/useLocale.test.js @@ -1,6 +1,6 @@ import { useDataQuery } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' -import { renderHook } from '@testing-library/react-hooks' +import { renderHook, waitFor } from '@testing-library/react' import moment from 'moment' import { useCurrentUserLocale } from './useLocale.js' @@ -84,7 +84,7 @@ describe('formerly problematic locales', () => { useDataQuery.mockReturnValue({ data: { userSettings: { keyUiLocale: 'pt_BR' } }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('ltr') // Notice different locale formats @@ -109,7 +109,7 @@ describe('formerly problematic locales', () => { useDataQuery.mockReturnValue({ data: { userSettings: { keyUiLocale: 'ar_EG' } }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('rtl') expect(result.current.locale.baseName).toBe('ar-EG') @@ -127,7 +127,7 @@ describe('formerly problematic locales', () => { useDataQuery.mockReturnValue({ data: { userSettings: { keyUiLocale: 'uz_UZ_Cyrl' } }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('ltr') expect(result.current.locale.baseName).toBe('uz-Cyrl-UZ') @@ -141,7 +141,7 @@ describe('formerly problematic locales', () => { useDataQuery.mockReturnValue({ data: { userSettings: { keyUiLocale: 'uz_UZ_Latn' } }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('ltr') expect(result.current.locale.baseName).toBe('uz-Latn-UZ') @@ -166,7 +166,7 @@ describe('other userSettings cases', () => { userSettings: { keyUiLocale: 'en', keyUiLanguageTag: 'pt-BR' }, }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('ltr') expect(result.current.locale.baseName).toBe('pt-BR') @@ -180,7 +180,7 @@ describe('other userSettings cases', () => { useDataQuery.mockReturnValue({ data: { userSettings: {} }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('rtl') expect(result.current.locale.baseName).toBe('ar-EG') @@ -194,7 +194,7 @@ describe('other userSettings cases', () => { useDataQuery.mockReturnValue({ data: { userSettings: { keyUiLocale: 'shouldCauseError' } }, }) - const { result, waitFor } = renderHook(() => useCurrentUserLocale()) + const { result } = renderHook(() => useCurrentUserLocale()) expect(result.current.direction).toBe('rtl') expect(result.current.locale.baseName).toBe('ar-EG') diff --git a/cli/config/d2.pwa.config.js b/cli/config/d2.pwa.config.js deleted file mode 100644 index d9e9b0c3a..000000000 --- a/cli/config/d2.pwa.config.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Default config for PWA properties in `d2.config.js`. They are kept separate - * from other defaults so they aren't included in the `d2.config.js` created - * when a new app is initialized with `d2 app scripts init` - */ -module.exports = { - pwa: { - /** - * If true, service worker is registered to perform offline caching - * and use of cacheable sections & recording mode is enabled - */ - enabled: false, - caching: { - /** - * If true, don't cache requests to exteral domains in app shell. - * Doesn't affect recording mode - */ - omitExternalRequestsFromAppShell: false, - /** Deprecated version of above */ - omitExternalRequests: false, - /** - * Don't cache URLs matching patterns in this array in app shell. - * Doesn't affect recording mode - */ - patternsToOmitFromAppShell: [], - /** Deprecated version of above */ - patternsToOmit: [], - /** - * Don't cache URLs matching these patterns in recorded sections. - * Can still be cached in app shell unless filtered there too. - */ - patternsToOmitFromCacheableSections: [], - /** - * In addition to the contents of an app's 'build' folder, other - * URLs can be precached by adding them to this list which will - * add them to the precache manifest at build time. The format of - * this list must match the Workbox precache list format: - * https://developers.google.com/web/tools/workbox/modules/workbox-precaching#explanation_of_the_precache_list - */ - additionalManifestEntries: [], - /** - * By default, all the contents of the `build` folder are added to - * the precache to give the app the best chances of functioning - * completely while offline. Developers may choose to omit some - * of these files (for example, thousands of font or image files) - * if they cause cache bloat and the app can work fine without - * them precached. See LIBS-482 - * - * The globs should be relative to the public dir of the built app. - * Used in injectPrecacheManifest.js - */ - globsToOmitFromPrecache: [], - }, - }, -} diff --git a/cli/config/d2ConfigDefaults.js b/cli/config/d2ConfigDefaults.js new file mode 100644 index 000000000..736ce0901 --- /dev/null +++ b/cli/config/d2ConfigDefaults.js @@ -0,0 +1,25 @@ +/** + * Note that these values are different from the boilerplate files that + * are copied when using `d2-app-scripts init` -- these are used as defaults + * when parsing d2 config of existing apps + * + * The boilerplate files live in /config/init/ + */ + +const defaultsApp = { + type: 'app', + + entryPoints: { + app: './src/App.jsx', + }, +} + +const defaultsLib = { + type: 'lib', + + entryPoints: { + lib: './src/index.jsx', + }, +} + +module.exports = { defaultsApp, defaultsLib } diff --git a/cli/config/d2ConfigDefaults.typescript.js b/cli/config/d2ConfigDefaults.typescript.js new file mode 100644 index 000000000..dc21f88dc --- /dev/null +++ b/cli/config/d2ConfigDefaults.typescript.js @@ -0,0 +1,17 @@ +const defaultsApp = { + type: 'app', + + entryPoints: { + app: './src/App.tsx', + }, +} + +const defaultsLib = { + type: 'lib', + + entryPoints: { + lib: './src/index.tsx', + }, +} + +module.exports = { defaultsApp, defaultsLib } diff --git a/cli/config/jest.config.js b/cli/config/jest.config.js index a2a059a18..4277b08fe 100644 --- a/cli/config/jest.config.js +++ b/cli/config/jest.config.js @@ -3,6 +3,9 @@ module.exports = { transform: { '^.+\\.[t|j]sx?$': require.resolve('./jest.transform.js'), }, + // Need to apply babel transformations to modules under moment/dist/, + // which use ES module format. See localeUtils.js in the adapter + transformIgnorePatterns: ['/node_modules/(?!moment/dist/)'], moduleNameMapper: { '\\.(css|less)$': require.resolve('./jest.identity.mock.js'), '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': diff --git a/cli/config/makeViteConfig.mjs b/cli/config/makeViteConfig.mjs new file mode 100644 index 000000000..c393a53c8 --- /dev/null +++ b/cli/config/makeViteConfig.mjs @@ -0,0 +1,192 @@ +import react from '@vitejs/plugin-react' +import { + defineConfig, + mergeConfig, + searchForWorkspaceRoot, + transformWithEsbuild, +} from 'vite' +import dynamicImport from 'vite-plugin-dynamic-import' + +// This file is used to create config to use with the Vite Node API +// (i.e. vite.createServer() and vite.build()) +// It uses ESM format and has an .mjs extension, since the CJS build of +// Vite's Node API is deprecated and will be removed in v6 +// https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated + +/** + * Allows JSX in .js files: + * Vite normally throws an error when JSX syntax is used in a file without a + * .jsx or .tsx extension. This is by design, in order to improve transform + * performance by not parsing JS files for JSX. + * + * This plugin and the `optimizeDeps` options in this config object, + * along with the `jsxRuntime: 'classic'` option in the React plugin of the main + * config, can enable support for JSX in .js files. This config object is + * merged with the main config if the `--allowJsxInJs` flag is passed + * to the CLI + * + * ! deprecated -- this config has a performance cost, especially on startup + */ +const jsxInJsConfig = { + plugins: [ + { + name: 'treat-js-files-as-jsx', + async transform(code, id) { + if (!id.match(/src\/.*\.js$/)) { + return null + } + // Use the exposed transform from vite, instead of directly + // transforming with esbuild + return transformWithEsbuild(code, id, { + loader: 'jsx', + jsx: 'automatic', + }) + }, + }, + ], + optimizeDeps: { + force: true, + esbuildOptions: { loader: { '.js': 'jsx' } }, + }, +} + +const momentLocaleRegex = /moment\/dist\/locale\/(.*)\.js$/ +/** + * Configure chunk output a bit to make a tidier build folder: + * Assign moment locale chunks into their own dir + * (they are already individually split out due to the dynamic import + * in localeUtils.js in the adapter) + */ +const handleChunkFileNames = (info) => { + const { facadeModuleId } = info // This id is the module's filepath + return momentLocaleRegex.test(facadeModuleId) + ? 'assets/moment-locales/[name]-[hash].js' + : 'assets/[name]-[hash].js' // the Rollup default +} + +const fontRegex = /\.woff2?/ +/** Tidy up fonts in the build/assets folder */ +const handleAssetFileNames = ({ name }) => { + return fontRegex.test(name) + ? 'assets/fonts/[name]-[hash][extname]' + : 'assets/[name]-[hash][extname]' // the Rollup default +} + +/** + * Setting up static variable replacements at build time. + * Vite adds env vars (from .env files, user env, and CLI args) to + * `import.meta.env`; for backwards compatibility and generalization, we also + * add those to `process.env`. + * + * Note that variables added to `import.meta.env` here will be available in + * index.html, e.g. import.meta.env.DHIS2_APP_NAME will populate + * %DHIS2_APP_NAME% in HTML + * + * Uses individual properties for drop-in replacements instead of a whole + * object, which allows for better dead code elimination. + * + * For env vars for now, we keep the behavior in /src/lib/shell/env.js: + * loading, filtering, and prefixing env vars for CRA. + * Once we remove support for those variables, we just need: + * 1. the Shell env vars, e.g. DHIS2_APP_NAME, -_VERSION, etc. This need to + * be added to the env by the `define` configuration below + * 2. the user's env, which can be loaded and filtered by Vite's + * `vite.loadEnv(mode, process.cwd(), ['DHIS2_'])`, + * and don't need to use `define`; they just need the envPrefix config. + */ +const getDefineOptions = (env) => { + const defineOptions = {} + Object.entries(env).forEach(([key, val]) => { + // Each `val` should be a string already, but we need to stringify again + // for it to appear as a string in the resulting code: + // '"value"' here => 'value' in the code + const stringifiedVal = JSON.stringify(val) + + defineOptions[`process.env.${key}`] = stringifiedVal + // 'DHIS2_'-prefixed vars go on import.meta.env too + if (key.startsWith('DHIS2_')) { + defineOptions[`import.meta.env.${key}`] = stringifiedVal + } + }) + return defineOptions +} + +const getBuildInputs = (config, paths) => { + const inputs = {} + if (config.entryPoints.app) { + inputs.main = paths.shellIndexHtml + } + if (config.entryPoints.plugin) { + inputs.plugin = paths.shellPluginHtml + } + return inputs +} + +// https://vitejs.dev/config/ +export default ({ paths, config, env, host, force, allowJsxInJs }) => { + const baseConfig = defineConfig({ + // Need to specify the location of the app root, since this CLI command + // gets run in a different directory than the bootstrapped app + root: paths.shell, + + // By default, assets are resolved to the root of the domain ('/'), but + // deployed apps aren't served from there. + // This option is basically the same as PUBLIC_URL for CRA and Parcel. + // Works for both dev and production. + // Gets applied to import.meta.env.BASE_URL in the runtime code + base: './', + + // Expose env vars with DHIS2_ prefix in index.html and on + // import.meta.env -- process.env.REACT_APP_DHIS2_... variables should + // migrate to the import.meta.env format + // https://vitejs.dev/config/shared-options.html#envprefix + envPrefix: 'DHIS2_', + + // Static replacement of vars at build time + define: getDefineOptions(env), + + server: { + host, + // By default, Vite allows serving files from a workspace root or + // falls back to the app root. Since we run the app in .d2/shell/, + // if the app is not in a workspace, files in cwd/node_modules can + // be out of reach (like fonts, which don't get bundled). + // Start the workspace search from cwd so it falls back to that + // when not in a workspace; then fonts in node_modules are usable + // https://vitejs.dev/config/server-options.html#server-fs-allow + fs: { allow: [searchForWorkspaceRoot(process.cwd())] }, + }, + + build: { + outDir: 'build', + rollupOptions: { + input: getBuildInputs(config, paths), + output: { + chunkFileNames: handleChunkFileNames, + assetFileNames: handleAssetFileNames, + }, + }, + }, + + plugins: [ + /** + * Allows the dynamic import of `moment/dist/locale/${locale}` + * in /adapter/src/utils/localeUtils.js. + * Also works for other "bare" packages; approximates behavior of + * inline `webpackChunkName` usage. Third-party plugin. + */ + dynamicImport(), + react({ + babel: { plugins: ['styled-jsx/babel'] }, + // ! deprecated with other jsx-in-js config + // This option allows HMR of JSX-in-JS files, + // but it isn't possible to add with merge config: + jsxRuntime: allowJsxInJs ? 'classic' : 'automatic', + }), + ], + + optimizeDeps: { force }, + }) + + return allowJsxInJs ? mergeConfig(baseConfig, jsxInJsConfig) : baseConfig +} diff --git a/cli/config/plugin.webpack.config.js b/cli/config/plugin.webpack.config.js deleted file mode 100644 index 81ee88635..000000000 --- a/cli/config/plugin.webpack.config.js +++ /dev/null @@ -1,307 +0,0 @@ -// Based on CRA Webpack config - -const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin') -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent') -const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath') -const TerserPlugin = require('terser-webpack-plugin') -const webpack = require('webpack') -const WorkboxWebpackPlugin = require('workbox-webpack-plugin') -const { getPWAEnvVars } = require('../src/lib/pwa') -const getShellEnv = require('../src/lib/shell/env') -const makeBabelConfig = require('./makeBabelConfig') - -const babelWebpackConfig = { - babelrc: false, - configFile: false, - cacheDirectory: true, - cacheCompression: false, -} - -const cssRegex = /\.css$/ -const cssModuleRegex = /\.module\.css$/ - -module.exports = ({ env: webpackEnv, config, paths }) => { - const isProduction = webpackEnv === 'production' - const isDevelopment = !isProduction - - const publicPath = getPublicUrlOrPath( - isDevelopment, - null, - process.env.PUBLIC_URL - ) - - const shellEnv = getShellEnv({ - plugin: 'true', - requiredProps: config.requiredProps ? config.requiredProps.join() : '', - skipPluginLogic: config.skipPluginLogic, - name: config.title, - ...getPWAEnvVars(config), - }) - - // "style" loader turns CSS into JS modules that inject ) } diff --git a/examples/simple-app/src/Alerter.module.css b/examples/simple-app/src/Alerter.module.css new file mode 100644 index 000000000..9ff151b25 --- /dev/null +++ b/examples/simple-app/src/Alerter.module.css @@ -0,0 +1,6 @@ +.flexContainer { + display: flex; + align-items: center; + justify-content: space-between; + width: 500px; +} diff --git a/examples/simple-app/src/App.js b/examples/simple-app/src/App.jsx similarity index 68% rename from examples/simple-app/src/App.js rename to examples/simple-app/src/App.jsx index 3e1a3c037..78bf15760 100644 --- a/examples/simple-app/src/App.js +++ b/examples/simple-app/src/App.jsx @@ -1,8 +1,8 @@ import { useDataQuery } from '@dhis2/app-runtime' import moment from 'moment' import React from 'react' -import { Alerter } from './Alerter.js' -import style from './App.style.js' +import { Alerter } from './Alerter.jsx' +import styles from './App.module.css' import i18n from './locales/index.js' const query = { @@ -14,8 +14,7 @@ const query = { const Component = () => { const { error, loading, data } = useDataQuery(query) return ( -
- +
{error && ERROR} {loading && ...} {data && ( @@ -23,6 +22,10 @@ const Component = () => {

{i18n.t('Hello {{name}}', { name: data.me.name })}

{i18n.t('Have a great {{dayOfTheWeek}}!', { + // NB: This won't localize on a dev build due to + // Vite's monorepo dep pre-bundling behavior. + // `moment` localization works outside the monorepo + // and in production here though dayOfTheWeek: moment.weekdays(true)[moment().weekday()], })} diff --git a/cli/config/init.App.module.css b/examples/simple-app/src/App.module.css similarity index 89% rename from cli/config/init.App.module.css rename to examples/simple-app/src/App.module.css index a824cffd5..3ee1159e0 100644 --- a/cli/config/init.App.module.css +++ b/examples/simple-app/src/App.module.css @@ -1,9 +1,10 @@ -.container { +.appContainer { width: 100%; height: 100%; + display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 1rem; -} +} \ No newline at end of file diff --git a/examples/simple-app/src/App.style.js b/examples/simple-app/src/App.style.js deleted file mode 100644 index d728f2bbe..000000000 --- a/examples/simple-app/src/App.style.js +++ /dev/null @@ -1,14 +0,0 @@ -import css from 'styled-jsx/css' - -export default css` - div { - width: 100%; - height: 100%; - - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: 1rem; - } -` diff --git a/package.json b/package.json index 3ae750a66..35ba83237 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "11.7.5", + "version": "12.0.0-alpha.29", "private": true, "repository": { "type": "git", @@ -17,7 +17,7 @@ ] }, "devDependencies": { - "@dhis2/cli-style": "^10.4.3", + "@dhis2/cli-style": "^10.5.2", "@dhis2/cli-utils-docsite": "^3.0.0", "concurrently": "^6.0.0", "serve": "^12.0.0" diff --git a/pwa/package.json b/pwa/package.json index 04f368ca5..b42dcab7e 100644 --- a/pwa/package.json +++ b/pwa/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/pwa", - "version": "11.7.5", + "version": "12.0.0-alpha.29", "description": "", "license": "BSD-3-Clause", "publishConfig": { @@ -13,14 +13,13 @@ "deploy": "d2-app-scripts deploy" }, "devDependencies": { - "@dhis2/cli-app-scripts": "11.7.5" + "@dhis2/cli-app-scripts": "12.0.0-alpha.29" }, "dependencies": { "idb": "^6.0.0", - "workbox-core": "^6.1.5", - "workbox-precaching": "^6.1.5", - "workbox-routing": "^6.1.5", - "workbox-strategies": "^6.1.5" + "workbox-precaching": "^7.1.0", + "workbox-routing": "^7.1.0", + "workbox-strategies": "^7.1.0" }, "main": "./build/cjs/index.js", "module": "./build/es/index.js", diff --git a/pwa/src/service-worker/other-strategies.js b/pwa/src/service-worker/other-strategies.js new file mode 100644 index 000000000..7df8d3ee8 --- /dev/null +++ b/pwa/src/service-worker/other-strategies.js @@ -0,0 +1,86 @@ +import { Strategy } from 'workbox-strategies' + +const versionRegex = /\?[tv]=[0-9a-z]+$/ + +const isLocalFile = (request) => { + const appRoot = new URL('.', self.location.href).href + return request.url.startsWith(appRoot) +} + +/** Remove version tag on local files */ +const getCacheKey = (request) => { + const versionMatch = request.url.match(versionRegex) + if (!isLocalFile(request) || !versionMatch) { + // don't need to handle this request + return request + } + // else return unversioned url + return request.url.substring(0, versionMatch.index) +} + +/** + * During development, the Vite server can end up creating a lot of + * requests that add to the cache more and more, since assets get new + * version hashes: + * Each time the server restarts, dependencies get a new '?v=' + * param added to the URL and a new cache entry for it. + * Each time a source file is hot-updated, that file gets a new cache entry + * with a '?t=' param added to the URL. + * + * If, after fetching, we cache these requests using the URL _without_ the + * version tag as a key, we save adding lots of duplicate entries to the cache + * while still caching the latest version of the file. + * + * This also avoids a niche but crash-causing bug for PWA apps in development + * that happens after editing files locally to trigger HMR updates, then going + * offline and reloading the app + */ +export class DevNetworkFirst extends Strategy { + async _handle(request, handler) { + let error, response + try { + // Try to fetch over the network + // -- include version params here + response = await handler.fetch(request) + } catch (fetchError) { + error = fetchError + } + + // Handle local files with version params added + const cacheKey = getCacheKey(request) + + if (response && !error) { + // Successful -- try to cache + // Note: handler.cachePut doesn't cache 400+ & 500+ responses, + // but they will get get returned to the browser below + // for the client to handle + await handler.cachePut(cacheKey, response.clone()) + } else { + // Unsuccessful -- try cache for response to return + response = await handler.cacheMatch(cacheKey) + } + + return response + } +} + +/** + * Strategy for all other requests: try cache if network fails, + * but don't add anything to cache + */ +export class NetworkAndTryCache extends Strategy { + _handle(request, handler) { + return handler.fetch(request).catch((fetchErr) => { + // use caches.match here because it matches all caches; + // handler.cacheMatch only checks one + return caches.match(request).then((res) => { + // If not found in cache, throw original fetchErr + // (if there's a cache err, that will be returned) + if (!res) { + throw fetchErr + } + return res + }) + }) + } +} diff --git a/pwa/src/service-worker/set-up-service-worker.js b/pwa/src/service-worker/set-up-service-worker.js index 4bc5bfad7..960c539dd 100644 --- a/pwa/src/service-worker/set-up-service-worker.js +++ b/pwa/src/service-worker/set-up-service-worker.js @@ -4,7 +4,6 @@ import { NetworkFirst, NetworkOnly, StaleWhileRevalidate, - Strategy, } from 'workbox-strategies' import { swMsgs } from '../lib/constants.js' import { @@ -12,6 +11,7 @@ import { dhis2ConnectionStatusPlugin, initDhis2ConnectionStatus, } from './dhis2-connection-status' +import { DevNetworkFirst, NetworkAndTryCache } from './other-strategies.js' import { startRecording, completeRecording, @@ -26,7 +26,6 @@ import { setUpKillSwitchServiceWorker, getClientsInfo, claimClients, - CRA_MANIFEST_EXCLUDE_PATTERNS, } from './utils.js' export function setUpServiceWorker() { @@ -60,11 +59,8 @@ export function setUpServiceWorker() { // In development, static assets are handled by 'network first' strategy // and will be kept up-to-date. if (PRODUCTION_ENV) { - // Precache all of the assets generated by your build process. - // Their URLs are injected into the manifest variable below. - // This variable must be present somewhere in your service worker file, - // even if you decide not to use precaching. See https://cra.link/PWA. - // Includes all built assets and index.html + // Injection point for the precache manifest from workbox-build, + // a manifest of app assets to fetch and cache upon SW installation const precacheManifest = self.__WB_MANIFEST || [] // todo: also do this routing for plugin.html @@ -122,39 +118,15 @@ export function setUpServiceWorker() { return matchPrecache(indexUrl) }) } - // NOTE: This route must come before any precacheAndRoute calls + // NOTE: This route must come before any precacheAndRoute calls, since + // precacheAndRoute creates routes for ALL previously precached files registerRoute(navigationRouteMatcher, navigationRouteHandler) - // Handle the rest of files in the manifest - filter out index.html, - // and all moment-locales, which bulk up the precache and slow down - // installation significantly. Handle them network-first in app shell - const restOfManifest = precacheManifest.filter((e) => { - if (e === indexHtmlManifestEntry) { - return false - } - // Files from the precache manifest generated by CRA need to be - // managed here, because we don't have access to their webpack - // config - const entryShouldBeExcluded = CRA_MANIFEST_EXCLUDE_PATTERNS.some( - (pattern) => pattern.test(e.url) - ) - return !entryShouldBeExcluded - }) + // Handle the rest of files in the manifest - filter out index.html + const restOfManifest = precacheManifest.filter( + (e) => e !== indexHtmlManifestEntry + ) precacheAndRoute(restOfManifest) - - // Same thing for built plugin assets - const pluginPrecacheManifest = self.__WB_PLUGIN_MANIFEST || [] - precacheAndRoute(pluginPrecacheManifest) - - // Similar to above; manifest injection from `workbox-build` - // Precaches all assets in the shell's build folder except in `static` - // (which CRA's workbox-webpack-plugin handle smartly). - // Additional files to precache can be added using the - // `additionalManifestEntries` option in d2.config.js; see the docs and - // 'injectPrecacheManifest.js' in the CLI package. - // '[]' fallback prevents an error when switching pwa enabled to disabled - const sharedBuildManifest = self.__WB_BUILD_MANIFEST || [] - precacheAndRoute(sharedBuildManifest) } // Handling pings: only use the network, and don't update the connection @@ -187,34 +159,20 @@ export function setUpServiceWorker() { }) ) - // Network-first caching by default + // Network-first caching for everything else. + // Uses a custom strategy in dev mode to avoid bloating cache + // with file duplicates // * NOTE: there may be lazy-loading errors while offline in dev mode + const ResolvedNetworkFirst = PRODUCTION_ENV ? NetworkFirst : DevNetworkFirst registerRoute( ({ url }) => urlMeetsAppShellCachingCriteria(url), - new NetworkFirst({ + new ResolvedNetworkFirst({ cacheName: 'app-shell', plugins: [dhis2ConnectionStatusPlugin], }) ) - // Strategy for all other requests: try cache if network fails, - // but don't add anything to cache - class NetworkAndTryCache extends Strategy { - _handle(request, handler) { - return handler.fetch(request).catch((fetchErr) => { - // handler.cacheMatch doesn't work b/c it doesn't check all caches - return caches.match(request).then((res) => { - // If not found in cache, throw original fetchErr - // (if there's a cache err, that will be returned) - if (!res) { - throw fetchErr - } - return res - }) - }) - } - } - // Use fallback strategy as default + // Fallback: try cache if network fails, but don't cache anything setDefaultHandler( new NetworkAndTryCache({ plugins: [dhis2ConnectionStatusPlugin] }) ) diff --git a/pwa/src/service-worker/utils.js b/pwa/src/service-worker/utils.js index 4fd83b313..fa0a72335 100644 --- a/pwa/src/service-worker/utils.js +++ b/pwa/src/service-worker/utils.js @@ -11,13 +11,8 @@ const APP_ADAPTER_URL_PATTERNS = [ /\/api(\/\d+)?\/userSettings/, // useLocale /\/api(\/\d+)?\/me\?fields=id$/, // useVerifyLatestUser ] -// Note that the CRA precache manifest files start with './' -// TODO: Make this extensible with a d2.config.js option -export const CRA_MANIFEST_EXCLUDE_PATTERNS = [ - /^\.\/static\/js\/moment-locales\//, -] -// '[]' Fallback prevents error when switching from pwa enabled to disabled +// '[]' Fallback prevents error when undefined const APP_SHELL_URL_FILTER_PATTERNS = JSON.parse( process.env .REACT_APP_DHIS2_APP_PWA_CACHING_PATTERNS_TO_OMIT_FROM_APP_SHELL || diff --git a/shell/public/index.html b/shell/index.html similarity index 64% rename from shell/public/index.html rename to shell/index.html index 5eff6b809..dd10d7d18 100644 --- a/shell/public/index.html +++ b/shell/index.html @@ -10,6 +10,7 @@ + - %REACT_APP_DHIS2_APP_NAME% | DHIS2 + %DHIS2_APP_NAME% | DHIS2
+ + + + + +