Skip to content

Commit

Permalink
feat: pwa
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 9, 2018
1 parent 8adcabc commit 902f6c0
Show file tree
Hide file tree
Showing 47 changed files with 908 additions and 73 deletions.
5 changes: 5 additions & 0 deletions packages/@vue/cli-plugin-pwa/__test__/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"vue-libs/no-async-functions": 0
}
}
73 changes: 73 additions & 0 deletions packages/@vue/cli-plugin-pwa/__test__/pwa.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
jest.setTimeout(30000)

const fs = require('fs')
const path = require('path')
const portfinder = require('portfinder')
const { createServer } = require('http-server')
const { defaults } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')

let server, browser
test('pwa', async () => {
// it's ok to mutate here since jest loads each test in a separate vm
defaults.plugins['@vue/cli-plugin-pwa'] = {}
const project = await create('pwa-build', defaults)
expect(project.has('src/registerServiceWorker.js')).toBe(true)

const { stdout } = await project.run('vue-cli-service build')
expect(stdout).toMatch('Build complete.')

const distDir = path.join(project.dir, 'dist')
const hasFile = file => fs.existsSync(path.join(distDir, file))
expect(hasFile('index.html')).toBe(true)
expect(hasFile('favicon.ico')).toBe(true)
expect(hasFile('js')).toBe(true)
expect(hasFile('css')).toBe(true)

// PWA specific files
expect(hasFile('manifest.json')).toBe(true)
expect(hasFile('img/icons/android-chrome-512x512.png')).toBe(true)

// Make sure the base preload/prefetch are not affected
const index = await project.read('dist/index.html')
// should split and preload app.js & vendor.js
expect(index).toMatch(/<link rel=preload [^>]+app[^>]+\.js>/)
expect(index).toMatch(/<link rel=preload [^>]+vendor[^>]+\.js>/)
// should not preload manifest because it's inlined
expect(index).not.toMatch(/<link rel=preload [^>]+manifest[^>]+\.js>/)
// should inline manifest and wepback runtime
expect(index).toMatch('webpackJsonp')

// PWA specific directives
expect(index).toMatch(`<link rel=manifest href=/manifest.json>`)
expect(index).toMatch(`<!--[if IE]><link rel="shortcut icon" href="/favicon.ico"><![endif]-->`)
expect(index).toMatch(`<meta name=apple-mobile-web-app-capable content=yes>`)

// should import service worker script
const main = await project.read('src/main.js')
expect(main).toMatch(`import './registerServiceWorker'`)

const port = await portfinder.getPortPromise()
server = createServer({ root: distDir })

await new Promise((resolve, reject) => {
server.listen(port, err => {
if (err) return reject(err)
resolve()
})
})

const launched = await launchPuppeteer(`http://localhost:${port}/`)
browser = launched.browser

await new Promise(r => setTimeout(r, 500))
const logs = launched.logs
expect(logs.some(msg => msg.match(/Content is cached for offline use/))).toBe(true)
expect(logs.some(msg => msg.match(/This web app is being served cache-first/))).toBe(true)
})

afterAll(async () => {
await browser.close()
server.close()
})
14 changes: 14 additions & 0 deletions packages/@vue/cli-plugin-pwa/generator/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = api => {
api.render('./template')

api.postProcessFiles(files => {
const main = files['src/main.js']
if (main) {
// inject import for registerServiceWorker script into main.js
const lines = main.split('\n').reverse()
const lastImportIndex = lines.findIndex(line => line.match(/^import/))
lines[lastImportIndex] += `\nimport './registerServiceWorker'`
files['src/main.js'] = lines.reverse().join('\n') + '\n'
}
})
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "<%- rootOptions.projectName %>",
"short_name": "<%- rootOptions.projectName %>",
"icons": [
{
"src": "/img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#4DBA87"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable no-console */

import { register } from '@vue/cli-plugin-pwa/registerServiceWorker'

const serviceWorkerEventBus = register()

serviceWorkerEventBus.$on('new-content', () => {
console.log('New content is available; please refresh.')
})

serviceWorkerEventBus.$on('content-cached', () => {
console.log('Content is cached for offline use.')
})

serviceWorkerEventBus.$on('offline', () => {
console.log('No internet connection found. App is running in offline mode.')
})

serviceWorkerEventBus.$on('error', error => {
console.error('Error during service worker registration:', error)
})

export default serviceWorkerEventBus
38 changes: 38 additions & 0 deletions packages/@vue/cli-plugin-pwa/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
const name = api.service.pkg.name

// make sure the registerServiceWorker script is transpiled
webpackConfig.module
.rule('js')
.include
.add(require.resolve('./registerServiceWorker'))

// the pwa plugin hooks on to html-webpack-plugin
// and injects icons, manifest links & other PWA related tags into <head>
webpackConfig
.plugin('pwa')
.use(require('./lib/HtmlPwaPlugin'), [Object.assign({
name
}, options.pwa)])

// generate /service-worker.js in production mode
if (process.env.NODE_ENV === 'production') {
webpackConfig
.plugin('sw-precache')
.use(require('sw-precache-webpack-plugin'), [{
cacheId: name,
filename: 'service-worker.js',
staticFileGlobs: [`${options.outputDir}/**/*.{js,html,css}`],
minify: true,
stripPrefix: `${options.outputDir}/`
}])
}
})

// install dev server middleware for resetting service worker during dev
const createNoopServiceWorkerMiddleware = require('./lib/noopServiceWorkerMiddleware')
api.configureDevServer(app => {
app.use(createNoopServiceWorkerMiddleware())
})
}
Loading

0 comments on commit 902f6c0

Please sign in to comment.