Nuxt PWA 注册服务工作者未在本地主机外部注册
Nuxt PWA register service worker not registering outside localhost
我们正在尝试托管 PWA nuxt,服务人员将仅使用
在 localhost:3000 上注册
npm start
尝试从 192.168.0.2:3000 访问它会导致以下情况:
Page is not served from a secure origin No matching service worker
detected. You may need to reload the page, or check that the scope of
the service worker for the current page encloses the scope and start
URL from the manifest.
nuxt.config.js
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'ONX',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
//{src: '~/plugins/VueCtkDateTimePicker.js', ssr: false }, // datepicker plugin here
{ src: '~/plugins/DateTimePicker.js' },
{ src: '~/plugins/VueTabulator.js', mode: 'client', ssr: false }
// { src: '~/plugins/Filters.js' }
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
'@nuxtjs/device',
'@nuxtjs/pwa',
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'@nuxtjs/axios',
'@nuxtjs/proxy',
'@nuxt/content',
],
proxy: {
'/api': {
target: 'http://target:81',
pathRewrite: {
'^/api': '/',
},
},
changeOrigin: true,
},
bootstrapVue: {
// Install the `IconsPlugin` plugin (in addition to `BootstrapVue` plugin)
icons: true
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
extend(config, ctx) {
if (ctx.isDev) {
config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'
}
},
},
server: {
host: '0.0.0.0',
port: 8000, // default: 3000
},
pwa: {
manifest: {
"short_name": "my_project",
"name": "my_project",
"background_color": "white",
"display": "standalone",
"scope": "/",
"theme_color": "white"
},
}
}
sw.js
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
manifest.json - 在 chrome 调试器上可见
{
"name": "my_project",
"short_name": "my_project",
"icons": [
{
"src": "/_nuxt/icons/icon_64x64.64bc82.png",
"sizes": "64x64",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_120x120.64bc82.png",
"sizes": "120x120",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_144x144.64bc82.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_152x152.64bc82.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_192x192.64bc82.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_384x384.64bc82.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_512x512.64bc82.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "/?standalone=true",
"display": "standalone",
"background_color": "white",
"theme_color": "white",
"lang": "en",
"scope": "/"
}
对于可能遇到此问题的任何人, was right; I needed a valid certificate. How to generate a local cert using MKCert。
安装 MKcert 并将其添加到本地根 CA
mkcert -install
为本地应用程序创建证书(在应用程序的文件夹中运行)
mkcert 192.168.0.2
将证书添加到 nuxt.config.js
server: {
https: {
key: fs.readFileSync(path.resolve(__dirname, '192.168.0.2-key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, '192.168.0.2.pem'))
},
}
清理浏览器 cookie 和缓存
访问 https://192.168.0.2
,等等:可以安装 PWA 添加
手机使用说明
奖励: 对于试图让它在移动设备上运行的人:
将您的 rootCA.pem
转换为 rootCA.crt
。 How to convert pem to crt:
openssl x509 -outform der -in rootCA.pem -out rootCA.crt
将 crt
证书导入您的移动设备。 How to import CRT on android mobile。 (可能因设备而异。)
我们正在尝试托管 PWA nuxt,服务人员将仅使用
在 localhost:3000 上注册npm start
尝试从 192.168.0.2:3000 访问它会导致以下情况:
Page is not served from a secure origin No matching service worker detected. You may need to reload the page, or check that the scope of the service worker for the current page encloses the scope and start URL from the manifest.
nuxt.config.js
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'ONX',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
//{src: '~/plugins/VueCtkDateTimePicker.js', ssr: false }, // datepicker plugin here
{ src: '~/plugins/DateTimePicker.js' },
{ src: '~/plugins/VueTabulator.js', mode: 'client', ssr: false }
// { src: '~/plugins/Filters.js' }
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
'@nuxtjs/device',
'@nuxtjs/pwa',
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'@nuxtjs/axios',
'@nuxtjs/proxy',
'@nuxt/content',
],
proxy: {
'/api': {
target: 'http://target:81',
pathRewrite: {
'^/api': '/',
},
},
changeOrigin: true,
},
bootstrapVue: {
// Install the `IconsPlugin` plugin (in addition to `BootstrapVue` plugin)
icons: true
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
extend(config, ctx) {
if (ctx.isDev) {
config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'
}
},
},
server: {
host: '0.0.0.0',
port: 8000, // default: 3000
},
pwa: {
manifest: {
"short_name": "my_project",
"name": "my_project",
"background_color": "white",
"display": "standalone",
"scope": "/",
"theme_color": "white"
},
}
}
sw.js
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
manifest.json - 在 chrome 调试器上可见
{
"name": "my_project",
"short_name": "my_project",
"icons": [
{
"src": "/_nuxt/icons/icon_64x64.64bc82.png",
"sizes": "64x64",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_120x120.64bc82.png",
"sizes": "120x120",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_144x144.64bc82.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_152x152.64bc82.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_192x192.64bc82.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_384x384.64bc82.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_512x512.64bc82.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "/?standalone=true",
"display": "standalone",
"background_color": "white",
"theme_color": "white",
"lang": "en",
"scope": "/"
}
对于可能遇到此问题的任何人,
安装 MKcert 并将其添加到本地根 CA
mkcert -install
为本地应用程序创建证书(在应用程序的文件夹中运行)
mkcert 192.168.0.2
将证书添加到
nuxt.config.js
server: { https: { key: fs.readFileSync(path.resolve(__dirname, '192.168.0.2-key.pem')), cert: fs.readFileSync(path.resolve(__dirname, '192.168.0.2.pem')) }, }
清理浏览器 cookie 和缓存
访问
https://192.168.0.2
,等等:可以安装 PWA 添加
手机使用说明
奖励: 对于试图让它在移动设备上运行的人:
将您的
rootCA.pem
转换为rootCA.crt
。 How to convert pem to crt:openssl x509 -outform der -in rootCA.pem -out rootCA.crt
将
crt
证书导入您的移动设备。 How to import CRT on android mobile。 (可能因设备而异。)