Skip to content

Commit

Permalink
feat(cli): add pwa to plugins; fix plugin build details (#746)
Browse files Browse the repository at this point in the history
* chore(deps): add workbox webpack plugin

* feat(cli): add offline caching to plugins

* chore: todo around network handling

* chore(cli): update webpack config - css and file loaders

* fix(cli): handle fonts in plugin build

* fix: only inject plugin precache manifest in production

* feat(pwa): cache app adapter requests by default

* chore: comments
  • Loading branch information
KaiVandivier committed Sep 19, 2022
1 parent 94cd7eb commit fd920a4
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 14 deletions.
2 changes: 2 additions & 0 deletions adapter/src/components/ServerVersionProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const ServerVersionProvider = ({
setState({ loading: false, systemInfo })
})
.catch((e) => {
// Todo: If this is a network error, the app cannot load -- handle that gracefully here
// if (e === 'Network error') { ... }
setState({ loading: false, error: e })
})

Expand Down
2 changes: 1 addition & 1 deletion adapter/src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const request = (url, options) => {
})
.then((response) => {
if (response.status !== 200) {
reject('Request failed', response.statusText)
reject('Request failed ' + response.statusText)
return
}
try {
Expand Down
3 changes: 2 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
"terser-webpack-plugin": "^5.3.1",
"webpack": "^5.41.1",
"webpack-dev-server": "^4.7.4",
"workbox-build": "^6.1.5"
"workbox-build": "^6.1.5",
"workbox-webpack-plugin": "^6.5.4"
},
"bin": {
"d2-app-scripts": "./bin/d2-app-scripts"
Expand Down
5 changes: 4 additions & 1 deletion cli/src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,14 @@ const handler = async ({
await shell.build()

if (config.entryPoints.plugin) {
reporter.info('Building plugin...')
await plugin.build()
}

if (config.pwa.enabled) {
reporter.info('Injecting precache manifest...')
reporter.info(
'Injecting supplementary precache manifest...'
)
await injectPrecacheManifest(paths, config)
}
} else {
Expand Down
1 change: 1 addition & 0 deletions cli/src/lib/parseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const validateConfig = (config) => {
process.exit(1)
}
})
// Todo: make sure apps with plugins have `pwa_enabled: true` in the config
return true
}

Expand Down
8 changes: 6 additions & 2 deletions cli/src/lib/plugin/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ const { reporter } = require('@dhis2/cli-helpers-engine')
const webpack = require('webpack')
const webpackConfigFactory = require('./webpack.config')

module.exports = async ({ paths }) => {
module.exports = async ({ config, paths }) => {
reporter.debug('Building plugin...')

const webpackConfig = webpackConfigFactory({ env: 'production', paths })
const webpackConfig = webpackConfigFactory({
env: 'production',
config,
paths,
})
const compiler = webpack(webpackConfig)
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
Expand Down
9 changes: 7 additions & 2 deletions cli/src/lib/plugin/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackConfigFactory = require('./webpack.config')

module.exports = async ({ port, paths }) => {
const webpackConfig = webpackConfigFactory({ env: 'production', paths })
module.exports = async ({ port, config, paths }) => {
const webpackConfig = webpackConfigFactory({
// todo: change to development, but this creates a compilation error
env: 'production',
config,
paths,
})
const compiler = webpack(webpackConfig)

const host = process.env.HOST || 'localhost'
Expand Down
49 changes: 43 additions & 6 deletions cli/src/lib/plugin/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ 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 makeBabelConfig = require('../../../config/makeBabelConfig')
const { getPWAEnvVars } = require('../pwa')
const getShellEnv = require('../shell/env')

const babelWebpackConfig = {
Expand All @@ -21,7 +23,7 @@ const babelWebpackConfig = {
const cssRegex = /\.css$/
const cssModuleRegex = /\.module\.css$/

module.exports = ({ env: webpackEnv, paths }) => {
module.exports = ({ env: webpackEnv, config, paths }) => {
const isProduction = webpackEnv === 'production'
const isDevelopment = !isProduction

Expand All @@ -31,7 +33,12 @@ module.exports = ({ env: webpackEnv, paths }) => {
process.env.PUBLIC_URL
)

const shellEnv = getShellEnv({ plugin: 'true' })
const shellEnv = getShellEnv({
plugin: 'true',
name: config.title,
// todo: need to make sure PWA is enabled for plugins
...getPWAEnvVars(config),
})

// "style" loader turns CSS into JS modules that inject <style> tags.
// "css" loader resolves paths in CSS and adds assets as dependencies.
Expand Down Expand Up @@ -99,6 +106,10 @@ module.exports = ({ env: webpackEnv, paths }) => {
chunkFilename: isProduction
? 'static/js/plugin-[name].[contenthash:8].chunk.js'
: 'static/js/plugin-[name].chunk.js',
// ! dhis2: this at least gets fonts to match the CRA build,
// but is re-outputting them
assetModuleFilename: 'static/media/[name].[hash][ext]',
// TODO: investigate dev source maps here (devtoolModuleFilenameTemplate)
},
optimization: {
minimize: isProduction,
Expand Down Expand Up @@ -180,6 +191,27 @@ module.exports = ({ env: webpackEnv, paths }) => {
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
// dhis2: Inject plugin static assets to the existing SW's precache manifest
process.env.NODE_ENV === 'production' &&
new WorkboxWebpackPlugin.InjectManifest({
swSrc: paths.shellBuildServiceWorker,
injectionPoint: 'self.__WB_PLUGIN_MANIFEST',
// Skip compiling the SW, which happens in the app build step
compileSrc: false,
dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
exclude: [
/\.map$/,
/asset-manifest\.json$/,
/LICENSE/,
// TODO dhis2: locales are weird in the plugin build -
// Ignore them in precache manifest for now
/moment-locales/,
],
// Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
}),
].filter(Boolean),
module: {
rules: [
Expand Down Expand Up @@ -223,6 +255,9 @@ module.exports = ({ env: webpackEnv, paths }) => {
use: getStyleLoaders({
importLoaders: 1,
sourceMap: true,
modules: {
mode: 'icss',
},
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
Expand All @@ -236,6 +271,7 @@ module.exports = ({ env: webpackEnv, paths }) => {
importLoaders: 1,
sourceMap: true,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
Expand All @@ -246,23 +282,24 @@ module.exports = ({ env: webpackEnv, paths }) => {
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [
/^$/,
/\.(js|mjs|jsx|ts|tsx)$/,
/\.html$/,
/\.json$/,
],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
type: 'asset/resource',
},
],
},
],
},
// Saves some chunk size logging
performance: false,
// stats: 'verbose',
}
}
8 changes: 7 additions & 1 deletion pwa/src/service-worker/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export function setUpServiceWorker() {
// Includes all built assets and index.html
const precacheManifest = self.__WB_MANIFEST || []

// Same thing for built plugin assets
const pluginPrecacheManifest = self.__WB_PLUGIN_MANIFEST || []
precacheAndRoute(pluginPrecacheManifest)

// todo: also do this routing for plugin.html
// Extract index.html from the manifest to precache, then route
// in a custom way
const indexHtmlManifestEntry = precacheManifest.find(({ url }) =>
Expand Down Expand Up @@ -128,7 +133,8 @@ export function setUpServiceWorker() {
// `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
precacheAndRoute(self.__WB_BUILD_MANIFEST || [])
const sharedBuildManifest = self.__WB_BUILD_MANIFEST || []
precacheAndRoute(sharedBuildManifest)
}

// Request handler during recording mode: ALL requests are cached
Expand Down
14 changes: 14 additions & 0 deletions pwa/src/service-worker/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import {
} from '../lib/sections-db.js'

const CACHE_KEEP_LIST = ['other-assets', 'app-shell']
const APP_ADAPTER_URL_PATTERNS = [
/\/api(\/\d+)?\/system\/info/, // from ServerVersionProvider
/\/api(\/\d+)?\/userSettings/, // useLocale
/\/api(\/\d+)?\/me\?fields=id$/, // useVerifyLatestUser
]

// '[]' Fallback prevents error when switching from pwa enabled to disabled
const APP_SHELL_URL_FILTER_PATTERNS = JSON.parse(
process.env
Expand Down Expand Up @@ -46,6 +52,14 @@ export function setUpKillSwitchServiceWorker() {
}

export function urlMeetsAppShellCachingCriteria(url) {
// Cache this request if it is important for the app adapter to load
const isAdapterRequest = APP_ADAPTER_URL_PATTERNS.some((pattern) =>
pattern.test(url.href)
)
if (isAdapterRequest) {
return true
}

// Don't cache if pwa.caching.omitExternalRequests in d2.config is true
if (
OMIT_EXTERNAL_REQUESTS_FROM_APP_SHELL &&
Expand Down
Loading

0 comments on commit fd920a4

Please sign in to comment.