The*_*igK 10 laravel progressive-web-apps laravel-mix workbox workbox-webpack-plugin
我正在尝试为我的应用程序构建 PWA;并且花了将近 48 小时试图弄清楚如何在 Laravel Mix 中使用 Workbox。具有讽刺意味的是,谷歌说 Workbox 旨在让事情变得简单!
呸!
好的,到目前为止我已经想通了——
我将需要使用 ,InjectManifest Plugin因为我想在我的 Service Worker 中集成推送通知服务
我不知道如何specifiy这些路径swSrc和swDest。
什么代码应该进入我的webpack.mix.js,我是否应该在我的resources/js文件夹中有一个临时的 service-worker 来在文件夹中创建一个新的 service worker public/。
有人可以帮忙吗?
PS:我几乎阅读了所有博客和帮助文章;但没有人谈论可靠地将 Workbox 与 Laravel 混合使用。真的很感激这里的一些帮助。
Ben*_*rey 10
我最近对此进行了大量研究,虽然这可能不是您问题的完整答案,但它应该为您或访问此页面的任何其他人提供足够的入门指导......
随着我学习和研究更多,我会添加到这个答案中。
出于此答案的目的,我假设您的 service worker 被称为service-worker.js,但是,您显然可以随心所欲地称呼它。
假设你在你的项目中使用了动态导入(如果你没有,你应该使用),你需要将 Laravel Mix 降级到版本 3。Laravel Mix 4 中有一个公认的错误,它会阻止 CSS 正确绑定,这将在 Webpack 5 发布之前不会修复。
此外,此答案中概述的步骤是专门为 Laravel Mix 3 配置的。
要解决的第二个问题是是否利用workbox-webpack-plugin来注入workbox全局 usingimportScripts或者是否应该禁用它( using importWorkboxFrom: 'disabled')并单独导入您需要的特定模块...
该文件指出:
使用 JavaScript 捆绑器时,您不需要(实际上也不应该使用)
workbox全局或workbox-sw模块,因为您可以直接导入单个包文件。
这意味着我们 应该使用import而不是注入importScripts.
但是,这里存在各种问题:
service-worker.js包含在构建清单中,因为这将被注入到预缓存清单中service-worker.js被版本化,production即名称应该始终是service-worker.js,而不是service-worker.123abc.js。InjectManifest 将无法注入清单,因为 service-worker.js文件在运行时不存在。因此,为了利用import而不是importScripts,我们必须有两个单独的 webpack(混合)配置(有关如何执行此操作的指导,请参阅结论)。我不是 100% 确定这是正确的,但我会在收到以下任一问题的答案后更新我的答案(请支持他们以增加收到答案的机会):
假设您正在使用InjectManifest而不是GenerateSW,您将需要编写自己的服务工作者,它会在每次构建时通过 webpack 插件将 JS 清单注入其中。很简单,这意味着您需要在源目录中创建一个文件,该文件将用作 Service Worker。
我的位于 src/js/service-worker.js(如果你在一个完整的 Laravel 项目中构建,这会有所不同,我只是在一个独立的应用程序中使用 Laravel Mix)
有多种方法可以做到这一点;有些人喜欢将内联 JS 注入 HTML 模板,但其他人,包括我自己,只需在他们的app.js. 无论哪种方式,代码都应该看起来像:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
Run Code Online (Sandbox Code Playgroud)
workbox全局或模块导入正如之前引用的文档中提到的,我们鼓励将特定需要的模块导入您的服务工作者,而不是使用workbox全局或workbox-sw模块。
有关如何使用各个模块以及如何实际编写 Service Worker 的更多信息,请参阅以下文档:
https://developers.google.com/web/tools/workbox/guides/using-bundlers
根据我的所有研究(仍在进行中),我采用了以下概述的方法。
在阅读之前,请记住这是为一个独立的静态 PWA(即不是一个完整的 Laravel 项目)配置的。
/src/service-worker.js (服务人员)使用诸如 之类的捆绑器时webpack,建议使用 utliliseimport以确保仅包含必要的workbox模块。这是我的服务工作者骨架:
import config from '~/config'; // This is where I store project based configurations
import { setCacheNameDetails } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { registerNavigationRoute } from 'workbox-routing';
// Set the cache details
setCacheNameDetails({
prefix: config.app.name.replace(/\s+/g, '-').toLowerCase(),
suffix: config.app.version,
precache: 'precache',
runtime: 'runtime',
googleAnalytics: 'ga'
});
// Load the assets to be precached
precacheAndRoute(self.__precacheManifest);
// Ensure all requests are routed to index.html (SPA)
registerNavigationRoute('/index.html');
Run Code Online (Sandbox Code Playgroud)
/package.json"scripts": {
"development": "npm run dev-service-worker && npm run dev-core",
"dev": "npm run development",
"dev-service-worker": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=service-worker.mix",
"dev-core": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=core.mix",
"watch": "npm run dev-core -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"production": "npm run prod-service-worker && npm run prod-core",
"prod": "npm run production",
"prod-service-worker": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=service-worker.mix",
"prod-core": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=core.mix"
}
Run Code Online (Sandbox Code Playgroud)
命令说明
npm run dev等)。查看已知问题npm run watchnpm run <environment>-service-worker 将在指定的环境中只构建 service workernpm run <environment>-core 将只在指定的环境中构建核心应用程序已知的问题
npm run watch. 到目前为止,我一直无法让它正常工作"devDependencies": {
"laravel-mix": "^3.0.0"
}
Run Code Online (Sandbox Code Playgroud)
这也可以通过运行来实现 npm install laravel-mix@3.0.0
/static/index.ejs此 HTML 模板用于生成单页应用程序index.html。这个模板依赖于被注入的 webpack manifest。
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" class="no-js">
<head>
<!-- General meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="<%= config.meta.description %>">
<meta name="rating" content="General">
<meta name="author" content="Sine Macula">
<meta name="robots" content="index, follow">
<meta name="format-detection" content="telephone=no">
<!-- Preconnect and prefetch urls -->
<link rel="preconnect" href="<%= config.api.url %>" crossorigin>
<link rel="dns-prefetch" href="<%= config.api.url %>">
<!-- Theme Colour -->
<meta name="theme-color" content="<%= config.meta.theme %>">
<!-- General link tags -->
<link rel="canonical" href="<%= config.app.url %>">
<!-- Manifest JSON -->
<link rel="manifest" href="<%= StaticAsset('/manifest.json') %>" crossorigin>
<!-- ----------------------------------------------------------------------
---- Icon Tags
---- ----------------------------------------------------------------------
----
---- The following will set up the favicons and the apple touch icons to be
---- used when adding the app to the homescreen of an iPhone, and to
---- display in the head of the browser.
----
---->
<!--[if IE]>
<link rel="shortcut icon" href="<%= StaticAsset('/favicon.ico') %>">
<![endif]-->
<link rel="apple-touch-icon" sizes="72x72" href="<%= StaticAsset('/apple-touch-icon-72x72.png') %>">
<link rel="apple-touch-icon" sizes="120x120" href="<%= StaticAsset('/apple-touch-icon-120x120.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= StaticAsset('/apple-touch-icon-180x180.png') %>">
<link rel="icon" type="image/png" sizes="16x16" href="<%= StaticAsset('/favicon-16x16.png') %>">
<link rel="icon" type="image/png" sizes="32x32" href="<%= StaticAsset('/favicon-32x32.png') %>">
<link rel="icon" type="image/png" sizes="192x192" href="<%= StaticAsset('/android-chrome-192x192.png') %>">
<link rel="icon" type="image/png" sizes="194x194" href="<%= StaticAsset('/favicon-194x194.png') %>">
<link rel="mask-icon" href="<%= StaticAsset('/safari-pinned-tab.svg') %>" color="<%= config.meta.theme %>">
<meta name="msapplication-TileImage" content="<%= StaticAsset('/mstile-144x144.png') %>">
<meta name="msapplication-TileColor" content="<%= config.meta.theme %>">
<!-- ----------------------------------------------------------------------
---- Launch Images
---- ----------------------------------------------------------------------
----
---- Define the launch 'splash' screen images to be used on iOS.
----
---->
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-640x1136.png') %>" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-750x1294.png') %>" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1242x2148.png') %>" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1125x2436.png') %>" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1536x2048.png') %>" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1668x2224.png') %>" media="(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-2048x2732.png') %>" media="(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<!-- ----------------------------------------------------------------------
---- Application Tags
---- ----------------------------------------------------------------------
----
---- Define the application specific tags.
----
---->
<meta name="application-name" content="<%= config.app.name %>">
<meta name="apple-mobile-web-app-title" content="<%= config.app.name %>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="<%= config.app.status_bar %>">
<meta name="mobile-web-app-capable" content="yes">
<meta name="full-screen" content="yes">
<meta name="browsermode" content="application">
<!-- ----------------------------------------------------------------------
---- Social Media and Open Graph Tags
---- ----------------------------------------------------------------------
----
---- The following will create objects for social media sites to read when
---- scraping the site.
----
---->
<!-- Open Graph -->
<meta property="og:site_name" content="<%= config.app.name %>">
<meta property="og:url" content="<%= config.app.url %>">
<meta property="og:type" content="website">
<meta property="og:title" content="<%= config.meta.title %>">
<meta property="og:description" content="<%= config.meta.description %>">
<meta property="og:image" content="<%= StaticAsset('/assets/images/brand/social-1200x630.jpg') %>">
<!-- Twitter -->
<meta name="twitter:card" content="app">
<meta name="twitter:site" content="<%= config.app.name %>">
<meta name="twitter:title" content="<%= config.meta.title %>">
<meta name="twitter:description" content="<%= config.meta.description %>">
<meta name="twitter:image" content="<%= StaticAsset('/assets/images/brand/social-440x220.jpg') %>">
<!-- ----------------------------------------------------------------------
---- JSON Linked Data
---- ----------------------------------------------------------------------
----
---- This will link the website to its associated social media page. This
---- adds to the credibility of the website as it allows search engines to
---- determine the following of the company via social media
----
---->
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Organization",
"name": "<%= config.company.name %>",
"url": "<%= config.app.url %>",
"sameAs": [<%= '"' + Object.values(config.company.social).map(x => x.url).join('","') + '"' %>]
}
</script>
<!-- Define the page title -->
<title><%= config.meta.title %></title>
<!-- Generate the prefetch/preload links -->
<% webpack.chunks.slice().reverse().forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(js|css)$/)) { %>
<link rel="<%= chunk.initial ? 'preload' : 'prefetch' %>" href="<%= StaticAsset(file) %>" as="<%= file.match(/\.css$/) ? 'style' : 'script' %>">
<% } %>
<% }) %>
<% }) %>
<!-- Include the core styles -->
<% webpack.chunks.forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(css)$/) && chunk.initial) { %>
<link rel="stylesheet" href="<%= StaticAsset(file) %>">
<% } %>
<% }) %>
<% }) %>
</head>
<body ontouchstart="">
<!-- No javascript error -->
<noscript>JavaScript turned off...</noscript>
<!-- The Vue JS app element -->
<div id="app"></div>
<!-- Include the core scripts -->
<% webpack.chunks.slice().reverse().forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(js)$/) && chunk.initial) { %>
<script type="text/javascript" src="<%= StaticAsset(file) %>"></script>
<% } %>
<% }) %>
<% }) %>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
/service-worker.mix.js (构建服务工作者)此混合配置将构建您的 Service Worker ( service-worker.js),并将其放入/dist.
注意:我喜欢在dist每次构建项目时清理我的文件夹,并且由于此功能必须在构建过程的这个阶段运行,因此我将其包含在以下配置中。
const mix = require('laravel-mix');
const path = require('path');
// Set the public path
mix.setPublicPath('dist/');
// Define all the javascript files to be compiled
mix.js('src/js/service-worker.js', 'dist');
// Load any plugins required to compile the files
const Dotenv = require('dotenv-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// Define the required plugins for webpack
const plugins = [
// Grant access to the environment variables
new Dotenv,
// Ensure the dist folder is cleaned for each build
new CleanWebpackPlugin
];
// Extend the default Laravel Mix webpack configuration
mix.webpackConfig({
plugins,
resolve: {
alias: {
'~': path.resolve('')
}
}
});
// Disable mix-manifest.json (remove this for Laravel projects)
Mix.manifest.refresh = () => void 0;
Run Code Online (Sandbox Code Playgroud)
/core.mix.js (构建应用程序)此混合配置将构建您的主应用程序并将其放置在/dist/js.
这种混合配置有各种关键部分,每个部分都在其中的评论中进行了明确概述。这些是顶级领域:
app.js、manifest.js和vendor.js(和动态导入)laravel-mix-versionhash被使用html-webpack-plugin用于index.html基于index.ejs模板生成(见上文)webpack-pwa-manifest 用于生成基于清单的copy-webpack-plugin 用于将静态文件复制到 /dist目录,并将任何必要的图标复制到站点根目录imagemin-webpack-plugin 用于压缩任何静态图像 productionworkbox-webpack-plugin用于将 webpack 清单注入到 Service Worker 中使用的预缓存数组中。InjectManifest被使用,而不是GenerateSW上面可能有补充,但几乎所有内容都由以下代码中的注释描述:
const config = require('./config'); // This is where I store project based configurations
const mix = require('laravel-mix');
const path = require('path');
const fs = require('fs');
// Include any laravel mix plugins
// NOTE: not needed in Laravel projects
require('laravel-mix-versionhash');
// Set the public path
mix.setPublicPath('dist/');
// Define all the SASS files to be compiled
mix.sass('src/sass/app.scss', 'dist/css');
// Define all the javascript files to be compiled
mix.js('src/js/app.js', 'dist/js');
// Split the js into bundles
mix.extract([
// Define the libraries to extract to `vendor`
// e.g. 'vue'
]);
// Ensure the files are versioned when running in production
// NOTE: This is not needed in Laravel projects, you simply need
// run `mix.version`
if (mix.inProduction()) {
mix.versionHash({
length: 8
});
}
// Set any necessary mix options
mix.options({
// This doesn't do anything yet, but once the new version
// of Laravel Mix is released, this 'should' extract the
// styles from the Vue components and place them in a
// css file, as opposed to placing them inline
//extractVueStyles: true,
// Ensure the urls are not processed
processCssUrls: false,
// Apply any postcss plugins
postCss: [
require('css-declaration-sorter'),
require('autoprefixer')
]
});
// Disable mix-manifest.json
// NOTE: not needed in Laravel projects
Mix.manifest.refresh = () => void 0;
// Load any plugins required to compile the files
const Dotenv = require('dotenv-webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackPwaManifest = require('webpack-pwa-manifest');
const { InjectManifest } = require('workbox-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
// Define the required plugins for webpack
const plugins = [
// Grant access to the environment variables
new Dotenv,