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 e7f8a08b5..852355167 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@v2 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@v2 with: 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/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/package.json b/cli/package.json index 4a962c853..8aba5025e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -2,7 +2,7 @@ "name": "@dhis2/cli-app-scripts", "version": "11.2.2", "engines": { - "node": ">=14" + "node": "^18.0.0 || >=20.0.0" }, "repository": { "type": "git", @@ -29,7 +29,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.6.0", "@dhis2/app-shell": "11.2.2", - "@dhis2/cli-helpers-engine": "^3.2.0", + "@dhis2/cli-helpers-engine": "^3.2.2", "@jest/core": "^27.0.6", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", "@yarnpkg/lockfile": "^1.1.0", diff --git a/cli/src/commands/build.js b/cli/src/commands/build.js index 20819901d..9cc6e10a3 100644 --- a/cli/src/commands/build.js +++ b/cli/src/commands/build.js @@ -10,7 +10,7 @@ const parseConfig = require('../lib/parseConfig') const { isApp } = require('../lib/parseConfig') const makePaths = require('../lib/paths') const makePlugin = require('../lib/plugin') -const { injectPrecacheManifest } = require('../lib/pwa') +const { injectPrecacheManifest, compileServiceWorker } = require('../lib/pwa') const makeShell = require('../lib/shell') const { validatePackage } = require('../lib/validatePackage') const { handler: pack } = require('./pack.js') @@ -139,6 +139,9 @@ const handler = async ({ } if (config.pwa.enabled) { + reporter.info('Compiling service worker...') + await compileServiceWorker({ config, paths, mode }) + reporter.info( 'Injecting supplementary precache manifest...' ) diff --git a/cli/src/commands/start.js b/cli/src/commands/start.js index 660cea866..0b28c475b 100644 --- a/cli/src/commands/start.js +++ b/cli/src/commands/start.js @@ -116,11 +116,9 @@ const handler = async ({ if (config.pwa.enabled) { reporter.info('Compiling service worker...') - await compileServiceWorker({ - config, - paths, - mode: 'development', - }) + await compileServiceWorker({ config, paths, mode }) + // don't need to inject precache manifest because no precaching + // is done in development environments } reporter.print('') @@ -132,6 +130,7 @@ const handler = async ({ ) reporter.print('') + // todo: split up app and plugin starts const shellStartPromise = shell.start({ port: newPort }) if (config.entryPoints.plugin) { diff --git a/cli/src/lib/compiler/compile.js b/cli/src/lib/compiler/compile.js index 94842eae1..8b0865727 100644 --- a/cli/src/lib/compiler/compile.js +++ b/cli/src/lib/compiler/compile.js @@ -10,14 +10,11 @@ const { createAppEntrypointWrapper, createPluginEntrypointWrapper, } = require('./entrypoints.js') -const { - extensionPattern, - normalizeExtension, -} = require('./extensionHelpers.js') +const { extensionPattern } = require('./extensionHelpers.js') const watchFiles = ({ inputDir, outputDir, processFileCallback, watch }) => { const compileFile = async (source) => { - const relative = normalizeExtension(path.relative(inputDir, source)) + const relative = path.relative(inputDir, source) const destination = path.join(outputDir, relative) reporter.debug( `File ${relative} changed or added... dest: `, @@ -125,7 +122,8 @@ const compile = async ({ watchFiles({ inputDir: paths.src, outputDir: outDir, - processFileCallback: compileFile, + // todo: handle lib compilations with Vite + processFileCallback: isAppType ? copyFile : compileFile, watch, }), isAppType && diff --git a/cli/src/lib/compiler/entrypoints.js b/cli/src/lib/compiler/entrypoints.js index 943d548b0..6814a3760 100644 --- a/cli/src/lib/compiler/entrypoints.js +++ b/cli/src/lib/compiler/entrypoints.js @@ -2,7 +2,6 @@ const path = require('path') const { reporter, chalk } = require('@dhis2/cli-helpers-engine') const fs = require('fs-extra') const { isApp } = require('../parseConfig') -const { normalizeExtension } = require('./extensionHelpers.js') const verifyEntrypoint = ({ entrypoint, basePath, resolveModule }) => { if (!entrypoint.match(/^(\.\/)?src\//)) { @@ -82,12 +81,11 @@ exports.verifyEntrypoints = ({ const getEntrypointWrapper = async ({ entrypoint, paths }) => { const relativeEntrypoint = entrypoint.replace(/^(\.\/)?src\//, '') - const outRelativeEntrypoint = normalizeExtension(relativeEntrypoint) const shellAppSource = await fs.readFile(paths.shellSourceEntrypoint) return shellAppSource .toString() - .replace(/'.\/D2App\/app'/g, `'./D2App/${outRelativeEntrypoint}'`) + .replace(/'.\/D2App\/app\.jsx'/g, `'./D2App/${relativeEntrypoint}'`) } exports.createAppEntrypointWrapper = async ({ entrypoint, paths }) => { diff --git a/cli/src/lib/paths.js b/cli/src/lib/paths.js index 59aa52e6f..047e4e498 100644 --- a/cli/src/lib/paths.js +++ b/cli/src/lib/paths.js @@ -39,7 +39,7 @@ module.exports = (cwd = process.cwd()) => { readmeDefault: path.join(__dirname, '../../config/init.README.md'), shellSource, - shellSourceEntrypoint: path.join(shellSource, 'src/App.js'), + shellSourceEntrypoint: path.join(shellSource, 'src/App.jsx'), shellSourcePublic: path.join(shellSource, 'public'), base, @@ -55,17 +55,17 @@ module.exports = (cwd = process.cwd()) => { i18nLocales: path.join(base, './src/locales'), d2: path.join(base, './.d2/'), - appOutputFilename: 'App.js', + appOutputFilename: 'App.jsx', shell: path.join(base, './.d2/shell'), shellSrc: path.join(base, './.d2/shell/src'), - shellAppEntrypoint: path.join(base, './.d2/shell/src/App.js'), + shellAppEntrypoint: path.join(base, './.d2/shell/src/App.jsx'), shellAppDirname, shellApp: path.join(base, `./.d2/shell/${shellAppDirname}`), shellPluginBundleEntrypoint: path.join( base, - './.d2/shell/src/plugin.index.js' + './.d2/shell/src/plugin.index.jsx' ), - shellPluginEntrypoint: path.join(base, './.d2/shell/src/Plugin.js'), + shellPluginEntrypoint: path.join(base, './.d2/shell/src/Plugin.jsx'), shellSrcServiceWorker: path.join( base, './.d2/shell/src/service-worker.js' diff --git a/cli/src/lib/pwa/compileServiceWorker.js b/cli/src/lib/pwa/compileServiceWorker.js index 0a2be598f..eaca5395d 100644 --- a/cli/src/lib/pwa/compileServiceWorker.js +++ b/cli/src/lib/pwa/compileServiceWorker.js @@ -10,10 +10,9 @@ const getPWAEnvVars = require('./getPWAEnvVars') * dir for use with a dev server. In production mode, compiles a minified * service worker and outputs it into the apps `build` dir. * - * Currently used only for 'dev' SWs, since CRA handles production bundling. - * TODO: Use this for production bundling as well, which gives greater control - * over 'injectManifest' configuration (CRA omits files > 2MB) and bundling - * options. + * This could be migrated to a Vite config. Note that it still needs to be + * separate from the main app's Vite build because the SW needs a + * single-file IIFE output * * @param {Object} param0 * @param {Object} param0.config - d2 app config @@ -23,22 +22,22 @@ const getPWAEnvVars = require('./getPWAEnvVars') */ function compileServiceWorker({ config, paths, mode }) { // Choose appropriate destination for compiled SW based on 'mode' - const outputPath = - mode === 'production' - ? paths.shellBuildServiceWorker - : paths.shellPublicServiceWorker + const isProduction = mode === 'production' + const outputPath = isProduction + ? paths.shellBuildServiceWorker + : paths.shellPublicServiceWorker const { dir: outputDir, base: outputFilename } = path.parse(outputPath) // This is part of a bit of a hacky way to provide the same env vars to dev // SWs as in production by adding them to `process.env` using the plugin // below. - // TODO: This could be cleaner if the production SW is built in the same - // way instead of using the CRA webpack config, so both can more easily - // share environment variables. + // TODO: This could be refactored to be simpler now that we're not using + // CRA to build the service worker const env = getEnv({ name: config.title, ...getPWAEnvVars(config) }) const webpackConfig = { mode, // "production" or "development" + devtool: isProduction ? false : 'source-map', entry: paths.shellSrcServiceWorker, output: { path: outputDir, diff --git a/cli/src/lib/pwa/getPWAEnvVars.js b/cli/src/lib/pwa/getPWAEnvVars.js index eb6e39a03..1d636d972 100644 --- a/cli/src/lib/pwa/getPWAEnvVars.js +++ b/cli/src/lib/pwa/getPWAEnvVars.js @@ -33,9 +33,14 @@ function stringifyPatterns(patternsList) { * @param {Object} config */ function getPWAEnvVars(config) { - if (!isApp(config.type) || !config.pwa.enabled) { + if (!isApp(config.type)) { return null } + if (!config.pwa.enabled) { + // Explicitly adding this value to the env helps pare down code in + // non-PWA apps when doing static bundle analysis + return { pwa_enabled: 'false' } + } return { pwa_enabled: JSON.stringify(config.pwa.enabled), pwa_caching_omit_external_requests_from_app_shell: JSON.stringify( diff --git a/cli/src/lib/pwa/injectPrecacheManifest.js b/cli/src/lib/pwa/injectPrecacheManifest.js index fa2d2a01e..dde112cb4 100644 --- a/cli/src/lib/pwa/injectPrecacheManifest.js +++ b/cli/src/lib/pwa/injectPrecacheManifest.js @@ -36,25 +36,24 @@ function logManifestOutput({ count, filePaths, size, warnings }) { * `workbox-build`. */ module.exports = function injectPrecacheManifest(paths, config) { - // See https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.injectManifest + // See https://developer.chrome.com/docs/workbox/modules/workbox-build#injectmanifest_mode const injectManifestOptions = { swSrc: paths.shellBuildServiceWorker, swDest: paths.shellBuildServiceWorker, globDirectory: paths.shellBuildOutput, globPatterns: ['**/*'], - // Skip index.html, (plugin.html,) and `static` directory; - // CRA's workbox-webpack-plugin handles it smartly globIgnores: [ - 'static/**/*', - paths.launchPath, - paths.pluginLaunchPath, + // skip moment locales -- they result in many network requests and + // slow down service worker installation + '**/moment-locales/*', + '**/*.map', ...config.pwa.caching.globsToOmitFromPrecache, ], additionalManifestEntries: config.pwa.caching.additionalManifestEntries, - injectionPoint: 'self.__WB_BUILD_MANIFEST', + injectionPoint: 'self.__WB_MANIFEST', // Skip revision hashing for files with hash or semver in name: - // (see https://regex101.com/r/z4Hy9k/1/ for RegEx details) - dontCacheBustURLsMatching: /\.[a-z0-9]{8}\.|\d+\.\d+\.\d+/, + // (see https://regex101.com/r/z4Hy9k/3/ for RegEx details) + dontCacheBustURLsMatching: /[.-][A-Za-z0-9-_]{8}\.|\d+\.\d+\.\d+/, } return injectManifest(injectManifestOptions).then(logManifestOutput) diff --git a/cli/src/lib/shell/env.js b/cli/src/lib/shell/env.js index 962517191..d3acf29e2 100644 --- a/cli/src/lib/shell/env.js +++ b/cli/src/lib/shell/env.js @@ -35,6 +35,8 @@ module.exports = ({ port, ...vars }) => { ...filterEnv(), ...makeShellEnv(vars), }), + ...filterEnv(), + ...makeShellEnv(vars), PORT: port, PUBLIC_URL: process.env.PUBLIC_URL, } diff --git a/cli/src/lib/shell/index.js b/cli/src/lib/shell/index.js index d2e75c946..d7b04a14c 100644 --- a/cli/src/lib/shell/index.js +++ b/cli/src/lib/shell/index.js @@ -22,7 +22,7 @@ module.exports = ({ config, paths }) => { build: async () => { await exec({ cmd: 'yarn', - args: ['run', 'build'], + args: ['build'], cwd: paths.shell, env: getEnv({ ...baseEnvVars, ...getPWAEnvVars(config) }), pipe: false, @@ -31,10 +31,11 @@ module.exports = ({ config, paths }) => { start: async ({ port }) => { await exec({ cmd: 'yarn', - args: ['run', 'start'], + args: ['start'], cwd: paths.shell, env: getEnv({ ...baseEnvVars, port, ...getPWAEnvVars(config) }), - pipe: false, + // this option allows the colorful and interactive output from Vite: + stdio: 'inherit', }) }, // TODO: remove? Test command does not seem to call this method diff --git a/examples/simple-app/d2.config.js b/examples/simple-app/d2.config.js index 715669fa0..5aebf50ce 100644 --- a/examples/simple-app/d2.config.js +++ b/examples/simple-app/d2.config.js @@ -8,7 +8,7 @@ const config = { // standalone: true, // Don't bake-in a DHIS2 base URL, allow the user to choose entryPoints: { - app: './src/App.js', + app: './src/App.jsx', }, dataStoreNamespace: 'testapp-namespace', diff --git a/examples/simple-app/src/Alerter.js b/examples/simple-app/src/Alerter.jsx similarity index 100% rename from examples/simple-app/src/Alerter.js rename to examples/simple-app/src/Alerter.jsx diff --git a/examples/simple-app/src/App.js b/examples/simple-app/src/App.jsx similarity index 74% rename from examples/simple-app/src/App.js rename to examples/simple-app/src/App.jsx index 3e1a3c037..ee37f898c 100644 --- a/examples/simple-app/src/App.js +++ b/examples/simple-app/src/App.jsx @@ -1,7 +1,7 @@ import { useDataQuery } from '@dhis2/app-runtime' import moment from 'moment' import React from 'react' -import { Alerter } from './Alerter.js' +import { Alerter } from './Alerter.jsx' import style from './App.style.js' import i18n from './locales/index.js' @@ -23,6 +23,10 @@ const Component = () => {