{"id":1595,"date":"2025-08-12T06:53:55","date_gmt":"2025-08-11T21:53:55","guid":{"rendered":"https:\/\/beeknowledge.co.jp\/?p=1595"},"modified":"2025-08-12T06:53:57","modified_gmt":"2025-08-11T21:53:57","slug":"%ef%bc%88pwa-laravel-windowswsl2-%e9%96%8b%e7%99%ba%ef%bc%89%e3%82%bb%e3%83%83%e3%83%88%e3%82%a2%e3%83%83%e3%83%97%e7%b6%9a%e3%81%8d","status":"publish","type":"post","link":"https:\/\/beeknowledge.co.jp\/?p=1595","title":{"rendered":"\uff08PWA + Laravel \/ Windows+WSL2 \u958b\u767a\uff09\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u7d9a\u304d"},"content":{"rendered":"\n<!doctype html>\n<html lang=\"ja\">\n<head>\n<meta charset=\"utf-8\" \/>\n<title>\u8ffd\u52a0\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u307e\u3068\u3081\uff08PWA + Laravel \/ Windows+WSL2 \u958b\u767a\uff09<\/title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n<style>\n  body{font-family:system-ui,\"Segoe UI\",\"Hiragino Kaku Gothic ProN\",\"Noto Sans JP\",Meiryo,sans-serif;line-height:1.7;color:#111;max-width:920px;margin:40px auto;padding:0 16px}\n  pre{background:#0f172a;color:#e2e8f0;padding:12px 14px;border-radius:10px;overflow:auto}\n  h1,h2{line-height:1.35}\n  .tip{color:#059669}\n  .warn{color:#b45309}\n  .bad{color:#b91c1c}\n  li{margin:.25rem 0}\n<\/style>\n<\/head>\n<body>\n\n<h1>\u8ffd\u52a0\u3067\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u305f\u5185\u5bb9\u307e\u3068\u3081<\/h1>\n<p>\u3053\u3053\u3067\u306f\u3001Windows \u4e0a\u3067 <strong>PWA(React\/Vite)<\/strong> \u3068 <strong>Laravel(API)<\/strong> \u3092\u4e26\u8d70\u3055\u305b\u308b\u305f\u3081\u306b\u8ffd\u52a0\u3067\u884c\u3063\u305f\u8a2d\u5b9a\u30fb\u30c8\u30e9\u30d6\u30eb\u5bfe\u5fdc\u3092\u3001\u518d\u73fe\u3067\u304d\u308b\u3088\u3046\u306b\u6574\u7406\u3057\u307e\u3059\u3002\u6700\u7d42\u7684\u306b <code>http:\/\/localhost:5173<\/code> \u3067 Vite+React \u306e\u521d\u671f\u753b\u9762\u3001<code>http:\/\/localhost:8000<\/code> \u3067 Laravel \u306e\u521d\u671f\u753b\u9762\u304c\u8868\u793a\u3067\u304d\u308b\u72b6\u614b\u307e\u3067\u69cb\u7bc9\u6e08\u307f\u3067\u3059\u3002<\/p>\n\n<hr>\n\n<h2>1) Windows\u6a5f\u80fd\u306e\u6709\u52b9\u5316\u3068 WSL2 \u8d77\u52d5<\/h2>\n<ul>\n  <li>\u300cWindows \u306e\u6a5f\u80fd\u300d\u3067 <strong>Linux \u7528 Windows \u30b5\u30d6\u30b7\u30b9\u30c6\u30e0<\/strong> \u3068 <strong>Virtual Machine Platform<\/strong> \u3092 ON\u3002<\/li>\n  <li>\u7ba1\u7406\u8005 PowerShell \u3067\u6a5f\u80fd\u3092\u6709\u52b9\u5316 \u2192 \u518d\u8d77\u52d5\uff1a<\/li>\n<\/ul>\n<pre><code>dism \/online \/enable-feature \/featurename:Microsoft-Windows-Subsystem-Linux \/all \/norestart\ndism \/online \/enable-feature \/featurename:VirtualMachinePlatform \/all \/norestart\n<\/code><\/pre>\n<ul>\n  <li>WSL &#038; Ubuntu \u306e\u5c0e\u5165\u30fb\u65e2\u5b9a\u5316\u30fb\u78ba\u8a8d\uff1a<\/li>\n<\/ul>\n<pre><code>wsl --install -d Ubuntu\nwsl --set-default-version 2\nwsl --status\nwsl -l -v           # NAME\/STATE\/VERSION \u3092\u78ba\u8a8d\nwsl -d Ubuntu       # Ubuntu \u3092\u8d77\u52d5\n<\/code><\/pre>\n<p class=\"tip\">\u88dc\u8db3\uff1a\u300c<code>wsl: command not found<\/code>\u300d\u306f\u5165\u529b\u30df\u30b9\uff08<em>wsl:<\/em> \u3068\u6253\u3063\u3066\u3044\u305f\uff09\u3002\u6b63\u3057\u304f\u306f <code>wsl<\/code>\u3002<\/p>\n\n<hr>\n\n<h2>2) WSL(Ubuntu) \u5074\u306e Node LTS + pnpm \u5c0e\u5165<\/h2>\n<pre><code>sudo apt update\ncurl -fsSL https:\/\/raw.githubusercontent.com\/nvm-sh\/nvm\/v0.39.7\/install.sh | bash\nsource ~\/.nvm\/nvm.sh\nnvm install --lts\ncorepack enable\ncorepack prepare pnpm@9 --activate\nnode -v && pnpm -v\n<\/code><\/pre>\n<p>\u4f5c\u696d\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306f Linux \u5074\uff08\u4f8b\uff1a<code>~\/src<\/code>\uff09\u306b\u7f6e\u304f\u3068\u9ad8\u901f\u3067\u3059\u3002<\/p>\n\n<hr>\n\n<h2>3) Vite + React\/TS \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u4f5c\u6210\u3068 PWA \u5316<\/h2>\n<pre><code>mkdir -p ~\/src && cd ~\/src\nnpm create vite@latest pwa-app -- --template react-ts\ncd pwa-app\npnpm i\npnpm add -D vite-plugin-pwa workbox-window\n<\/code><\/pre>\n<p>\u30a2\u30a4\u30b3\u30f3\u4f5c\u6210\uff08\u7c21\u6613\uff09\uff1a<\/p>\n<pre><code>mkdir -p public\/icons\nsudo apt -y install imagemagick\nconvert -size 192x192 xc:\"#0ea5e9\" public\/icons\/icon-192.png\nconvert -size 512x512 xc:\"#0ea5e9\" public\/icons\/icon-512.png\n<\/code><\/pre>\n<p><code>vite.config.ts<\/code> \u306b PWA \u8a2d\u5b9a\u3068 API \u30d7\u30ed\u30ad\u30b7\uff08Laravel:8000\uff09\u3092\u8ffd\u52a0\uff1a<\/p>\n<pre><code>import { defineConfig } from 'vite'\nimport react from '@vitejs\/plugin-react'\nimport { VitePWA } from 'vite-plugin-pwa'\n\nexport default defineConfig({\n  plugins: [\n    react(),\n    VitePWA({\n      registerType: 'autoUpdate',\n      manifest: {\n        name:'GPX\/Video Uploader', short_name:'Uploader',\n        start_url:'\/?source=pwa', display:'standalone',\n        background_color:'#ffffff', theme_color:'#0ea5e9',\n        icons:[\n          {src:'icons\/icon-192.png',sizes:'192x192',type:'image\/png'},\n          {src:'icons\/icon-512.png',sizes:'512x512',type:'image\/png'}\n        ]\n      },\n      workbox:{\n        navigateFallback:'\/index.html',\n        runtimeCaching:[{urlPattern:\/^\\\/api\\\/\/,handler:'NetworkFirst'}]\n      },\n      devOptions:{enabled:true}\n    })\n  ],\n  server:{ host:true, proxy:{ '\/api':{ target:'http:\/\/localhost:8000', changeOrigin:true } } }\n})\n<\/code><\/pre>\n<p>\u8d77\u52d5\u78ba\u8a8d\uff1a<\/p>\n<pre><code>pnpm dev\n# \u2192 http:\/\/localhost:5173 \u3067 Vite+React \u521d\u671f\u753b\u9762\uff08\u8868\u793a\u6e08\u307f\uff09\n<\/code><\/pre>\n\n<hr>\n\n<h2>4) PHP 8.3 + Composer + Laravel \u30d7\u30ed\u30b8\u30a7\u30af\u30c8<\/h2>\n<p>PHP 8.3 \u7cfb\u3092\u5c0e\u5165\uff08\u4e0d\u8db3\u62e1\u5f35\u304c\u51fa\u305f\u3089\u9069\u5b9c\u8ffd\u52a0\uff09\u3002<\/p>\n<pre><code>sudo apt update\nsudo apt install -y php8.3-cli php8.3-common php8.3-mbstring php8.3-xml \\\n  php8.3-curl php8.3-zip php8.3-sqlite3 php8.3-mysql php8.3-gd php8.3-intl\n<\/code><\/pre>\n<p>Composer \u3092\u5c0e\u5165\uff1a<\/p>\n<pre><code>sudo apt install -y curl unzip git\nphp -r \"copy('https:\/\/getcomposer.org\/installer','composer-setup.php');\"\nphp composer-setup.php --install-dir=\/usr\/local\/bin --filename=composer\nrm composer-setup.php\ncomposer -V\n<\/code><\/pre>\n<p>Laravel \u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u4f5c\u6210 &#038; \u4f9d\u5b58\u89e3\u6c7a\uff1a<\/p>\n<pre><code>cd ~\/src\ncomposer create-project laravel\/laravel laravel-app\ncd laravel-app\ncomposer install\ncp .env.example .env\nphp artisan key:generate\n<\/code><\/pre>\n\n<hr>\n\n<h2>5) DB\u8a2d\u5b9a\uff08SQLite\u3067\u6700\u77ed or PostgreSQL\u3067\u672c\u756a\u5bc4\u308a\uff09<\/h2>\n<h3>A. \u6700\u901f\uff1aSQLite<\/h3>\n<pre><code>mkdir -p database\n: > database\/database.sqlite\n# .env \u306f DB_CONNECTION=sqlite \u306e\u307e\u307e\nphp artisan migrate\n<\/code><\/pre>\n\n<h3>B. PostgreSQL \u3092\u4f7f\u3046\u5834\u5408<\/h3>\n<p>SSL\u30a8\u30e9\u30fc\uff08Unsupported SSL request\uff09\u304c\u51fa\u305f\u305f\u3081\u3001\u30ed\u30fc\u30ab\u30eb\u306f SSL \u7121\u52b9\u3067\u63a5\u7d9a\u3002<\/p>\n<pre><code># .env\uff08\u629c\u7c8b\uff09\nDB_CONNECTION=pgsql\nDB_HOST=127.0.0.1\nDB_PORT=5432\nDB_DATABASE=yourdb\nDB_USERNAME=youruser\nDB_PASSWORD=yourpass\nDB_SSLMODE=disable\n<\/code><\/pre>\n<pre><code>sudo apt install -y php8.3-pgsql\nphp artisan config:clear\nphp artisan migrate\n<\/code><\/pre>\n\n<hr>\n\n<h2>6) Laravel \u958b\u767a\u30b5\u30fc\u30d0\u8d77\u52d5\u3068\u78ba\u8a8d<\/h2>\n<pre><code>php artisan serve --host=0.0.0.0 --port=8000\n# \u2192 http:\/\/localhost:8000 \u3067 Laravel \u521d\u671f\u753b\u9762\uff08\u8868\u793a\u6e08\u307f\uff09\n<\/code><\/pre>\n<p class=\"warn\">\u3082\u3057\u300cCould not open input file: artisan\u300d\u2192 \u305d\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c Laravel \u30eb\u30fc\u30c8\u3067\u306f\u306a\u3044\u3002<br>\u300cvendor\/autoload.php \u304c\u7121\u3044\u300d\u2192 <code>composer install<\/code> \u3092\u672a\u5b9f\u884c\u3002<\/p>\n\n<hr>\n\n<h2>7) \u30d5\u30ed\u30f3\u30c8\u304b\u3089 API \u3092\u53e9\u304f\u6e96\u5099\uff08\u6700\u5c0f\uff09<\/h2>\n<p>PWA \u5074\u306e\u7c21\u6613\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u753b\u9762\uff08\u52d5\u753b+GPX+meta\uff09\u3092 <code>src\/App.tsx<\/code> \u306b\u5b9f\u88c5\u3057\u3001<code>\/api\/v1\/runs<\/code> \u3078 <code>FormData<\/code> \u9001\u4fe1\u3002Laravel \u5074\u306f\u30eb\u30fc\u30c8\u30fb\u30b3\u30f3\u30c8\u30ed\u30fc\u30e9\u3092\u7528\u610f\u3057\u3066 202\uff08\u53d7\u9818\uff09\u3092\u8fd4\u3059\u3002<\/p>\n<pre><code>\/\/ \u4f8b\uff1a\u30d5\u30ed\u30f3\u30c8\u9001\u4fe1\nconst fd = new FormData();\nfd.append('video', fileVideo);\nfd.append('gpx', fileGpx);\nfd.append('meta', JSON.stringify(meta));\nawait fetch('\/api\/v1\/runs', { method:'POST', body: fd });\n<\/code><\/pre>\n<p>Laravel \u5074\u306f <code>routes\/api.php<\/code> \u306b <code>POST \/api\/v1\/runs<\/code> \u3092\u8ffd\u52a0\u3057\u3001\u53d7\u9818\u2192\u4fdd\u5b58\u2192\u30ad\u30e5\u30fc\u6295\u5165\u306e\u9aa8\u683c\u3092\u4f5c\u308b\u60f3\u5b9a\u3002<\/p>\n\n<hr>\n\n<h2>8) \u5b9f\u6a5f(Android)\u30c6\u30b9\u30c8\u306e\u305f\u3081\u306e ADB \u9006\u30dd\u30fc\u30c8<\/h2>\n<pre><code>adb devices\nadb reverse tcp:5173 tcp:5173   # Vite\nadb reverse tcp:8000 tcp:8000   # Laravel\n# \u7aef\u672b\u306eChrome\u3067 http:\/\/localhost:5173 \u2192 \u30db\u30fc\u30e0\u306b\u8ffd\u52a0\u3067PWA\u8d77\u52d5\n<\/code><\/pre>\n\n<hr>\n\n<h2>9) \u3088\u304f\u3042\u3063\u305f\u30a8\u30e9\u30fc\u3068\u89e3\u6c7a\u30e1\u30e2<\/h2>\n<ul>\n  <li><strong>wsl: command not found<\/strong>\uff1aPowerShell\u4ee5\u5916\u3067\u5b9f\u884c or \u6587\u5b57\u5217\u30df\u30b9\u3002<code>wsl<\/code> \u3092 PowerShell \u3067\u3002<\/li>\n  <li><strong>artisan \u304c\u7121\u3044<\/strong>\uff1aLaravel \u30eb\u30fc\u30c8\u5916\u3002<code>find . -name artisan<\/code> \u3067\u5834\u6240\u3092\u78ba\u8a8d\u3002<\/li>\n  <li><strong>vendor\/autoload.php \u304c\u7121\u3044<\/strong>\uff1a<code>composer install<\/code> \u672a\u5b9f\u884c\u3002<\/li>\n  <li><strong>PostgreSQL Unsupported SSL request<\/strong>\uff1a<code>.env<\/code> \u306b <code>DB_SSLMODE=disable<\/code> \u3092\u8ffd\u52a0\u3001<code>php artisan config:clear<\/code>\u3002<\/li>\n  <li><strong>PWA\u30a2\u30a4\u30b3\u30f3\u304c\u7121\u3044<\/strong>\uff1a<code>public\/icons\/icon-192.png<\/code>\/<code>512.png<\/code> \u3092\u81ea\u4f5c\u914d\u7f6e\uff08\u30d1\u30b9\u3092\u30b3\u30de\u30f3\u30c9\u5b9f\u884c\u3057\u306a\u3044\uff09\u3002<\/li>\n<\/ul>\n\n<hr>\n\n<h2>10) \u6b21\u306b\u3084\u308b\u3053\u3068\uff08\u672c\u5b9f\u88c5\u3078\uff09<\/h2>\n<ul>\n  <li>Laravel\uff1a<code>POST \/api\/v1\/runs<\/code> \u306e\u672c\u5b9f\u88c5\uff08\u52d5\u753b\/GPX\u4fdd\u5b58\u2192\u30b8\u30e7\u30d6\u6295\u5165\uff09\u3002<\/li>\n  <li>\u30ad\u30e5\u30fc\uff08Redis\uff09\u5c0e\u5165\u3001\u63a8\u8ad6\u30b5\u30fc\u30d3\u30b9\u9023\u643a\uff08Python\/FastAPI\uff09\u3002<\/li>\n  <li>PWA\uff1aIndexedDB \u9001\u4fe1\u30ad\u30e5\u30fc\u3001S3 Multipart or tus \u3067\u5927\u5bb9\u91cf\/\u30ec\u30b8\u30e5\u30fc\u30e0\u5bfe\u5fdc\u3002<\/li>\n  <li>POI\u53ef\u8996\u5316\uff1aLeaflet\/MapLibre \u3067\u4e8b\u524d\u78ba\u8a8d UI\u3002<\/li>\n<\/ul>\n\n<p class=\"tip\">\u3053\u3053\u307e\u3067\u3067\u300c\u30d5\u30ed\u30f3\u30c8\/Vite\u8d77\u52d5\u300d\u300cAPI\/Laravel\u8d77\u52d5\u300d\u300cWSL2\u3067\u306e\u958b\u767a\u52d5\u7dda\u300d\u304c\u6574\u3044\u307e\u3057\u305f\u3002\u6b21\u306f\u4ed5\u69d8\u901a\u308a\u306e\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8\u3068\u975e\u540c\u671f\u30d1\u30a4\u30d7\u30e9\u30a4\u30f3\u3092\u5165\u308c\u3066\u3044\u3051\u3070OK\u3067\u3059\u3002\u00a9\u682a\u5f0f\u4f1a\u793e\u30d3\u30fc\u30fb\u30ca\u30ec\u30c3\u30b8\u30fb\u30c7\u30b6\u30a4\u30f3<\/p>\n\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>\u8ffd\u52a0\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u307e\u3068\u3081\uff08PWA + Laravel \/ Windows+WSL2 \u958b\u767a\uff09 \u8ffd\u52a0\u3067\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u305f\u5185\u5bb9\u307e\u3068\u3081 \u3053\u3053\u3067\u306f\u3001Windows \u4e0a\u3067 PWA(React\/Vite) \u3068 Laravel(API) \u3092 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"saved_in_kubio":false,"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"vkexunit_cta_each_option":"","footnotes":""},"categories":[6],"tags":[51,36],"class_list":["post-1595","post","type-post","status-publish","format-standard","hentry","category-programing","tag-51","tag-36"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/posts\/1595","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1595"}],"version-history":[{"count":2,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/posts\/1595\/revisions"}],"predecessor-version":[{"id":1597,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=\/wp\/v2\/posts\/1595\/revisions\/1597"}],"wp:attachment":[{"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1595"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1595"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/beeknowledge.co.jp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1595"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}