next-pwa
Version:
Next.js with PWA, powered by workbox.
147 lines (130 loc) • 4.34 kB
JavaScript
const path = require('path')
const fs = require('fs')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const getFallbackEnvs = ({ fallbacks, basedir, id, pageExtensions }) => {
let { document, data } = fallbacks
if (!document) {
let pagesDir = undefined
if (fs.existsSync(path.join(basedir, 'pages'))) {
pagesDir = path.join(basedir, 'pages')
} else if (fs.existsSync(path.join(basedir, 'src', 'pages'))) {
pagesDir = path.join(basedir, 'src', 'pages')
}
if (!pagesDir) return
const offlines = pageExtensions
.map(ext => path.join(pagesDir, `_offline.${ext}`))
.filter(entry => fs.existsSync(entry))
if (offlines.length === 1) {
document = '/_offline'
}
}
if (data && data.endsWith('.json')) {
data = path.posix.join('/_next/data', id, data)
}
const envs = {
__PWA_FALLBACK_DOCUMENT__: document || false,
__PWA_FALLBACK_IMAGE__: fallbacks.image || false,
__PWA_FALLBACK_AUDIO__: fallbacks.audio || false,
__PWA_FALLBACK_VIDEO__: fallbacks.video || false,
__PWA_FALLBACK_FONT__: fallbacks.font || false,
__PWA_FALLBACK_DATA__: data || false
}
if (Object.values(envs).filter(v => !!v).length === 0) return
console.log('> [PWA] Fallback to precache routes when fetch failed from cache or network:')
if (envs.__PWA_FALLBACK_DOCUMENT__) console.log(`> [PWA] document (page): ${envs.__PWA_FALLBACK_DOCUMENT__}`)
if (envs.__PWA_FALLBACK_IMAGE__) console.log(`> [PWA] image: ${envs.__PWA_FALLBACK_IMAGE__}`)
if (envs.__PWA_FALLBACK_AUDIO__) console.log(`> [PWA] audio: ${envs.__PWA_FALLBACK_AUDIO__}`)
if (envs.__PWA_FALLBACK_VIDEO__) console.log(`> [PWA] video: ${envs.__PWA_FALLBACK_VIDEO__}`)
if (envs.__PWA_FALLBACK_FONT__) console.log(`> [PWA] font: ${envs.__PWA_FALLBACK_FONT__}`)
if (envs.__PWA_FALLBACK_DATA__) console.log(`> [PWA] data (/_next/data/**/*.json): ${envs.__PWA_FALLBACK_DATA__}`)
return envs
}
const buildFallbackWorker = ({ id, fallbacks, basedir, destdir, minify, pageExtensions }) => {
const envs = getFallbackEnvs({ fallbacks, basedir, id, pageExtensions })
if (!envs) return
const name = `fallback-${id}.js`
const fallbackJs = path.join(__dirname, `fallback.js`)
webpack({
mode: 'none',
target: 'webworker',
entry: {
main: fallbackJs
},
resolve: {
extensions: ['.js'],
fallback: {
module: false,
dgram: false,
dns: false,
path: false,
fs: false,
os: false,
crypto: false,
stream: false,
http2: false,
net: false,
tls: false,
zlib: false,
child_process: false
}
},
module: {
rules: [
{
test: /\.js$/i,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
'next/babel',
{
'transform-runtime': {
corejs: false,
helpers: true,
regenerator: false,
useESModules: true
},
'preset-env': {
modules: false,
targets: 'chrome >= 56'
}
}
]
]
}
}
]
}
]
},
output: {
path: destdir,
filename: name
},
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [path.join(destdir, 'fallback-*.js'), path.join(destdir, 'fallback-*.js.map')]
}),
new webpack.EnvironmentPlugin(envs)
],
optimization: minify
? {
minimize: true,
minimizer: [new TerserPlugin()]
}
: undefined
}).run((error, status) => {
if (error || status.hasErrors()) {
console.error(`> [PWA] Failed to build fallback worker`)
console.error(status.toString({ colors: true }))
process.exit(-1)
}
})
return { fallbacks, name, precaches: Object.values(envs).filter(v => !!v) }
}
module.exports = buildFallbackWorker