UNPKG

5.06 kBJavaScriptView Raw
1const path = require('path')
2const swBuild = require('workbox-build')
3const { readFileSync, writeFileSync } = require('fs')
4const hashSum = require('hash-sum')
5const debug = require('debug')('nuxt:pwa')
6const { defaultsDeep } = require('lodash')
7
8const fixUrl = url => url.replace(/\/\//g, '/').replace(':/', '://')
9const isUrl = url => url.indexOf('http') === 0 || url.indexOf('//') === 0
10
11// =============================================
12// workboxModule
13// =============================================
14
15module.exports = function nuxtWorkbox (moduleOptions) {
16 if (this.options.dev) {
17 return
18 }
19
20 const hook = builder => {
21 debug('Adding workbox')
22 const options = getOptions.call(this, moduleOptions)
23 workboxInject.call(this, options)
24 emitAssets.call(this, options)
25 addTemplates.call(this, options)
26 }
27
28 this.nuxt.hook ? this.nuxt.hook('build:before', hook) : this.nuxt.plugin('build', hook)
29}
30
31// =============================================
32// getRouterBase
33// =============================================
34
35function getOptions (moduleOptions) {
36 // Router Base
37 const routerBase = this.options.router.base
38 let publicPath = fixUrl(`${routerBase}/${this.options.build.publicPath}`)
39 if (isUrl(this.options.build.publicPath)) { // CDN
40 publicPath = this.options.build.publicPath
41 if (publicPath.indexOf('//') === 0) {
42 publicPath = '/' + publicPath // Escape fixUrl
43 }
44 }
45
46 const defaults = {
47 autoRegister: true,
48 routerBase,
49 publicPath,
50 swSrc: path.resolve(this.options.buildDir, 'sw.template.js'),
51 swDest: path.resolve(this.options.srcDir, 'static', 'sw.js'),
52 directoryIndex: '/',
53 cacheId: process.env.npm_package_name || 'nuxt',
54 clientsClaim: true,
55 globPatterns: ['**/*.{js,css}'],
56 globDirectory: path.resolve(this.options.buildDir, 'dist'),
57 modifyUrlPrefix: {
58 '': fixUrl(publicPath)
59 },
60 _runtimeCaching: [
61 // Cache all _nuxt resources at runtime
62 // They are hashed by webpack so are safe to loaded by cacheFirst handler
63 {
64 urlPattern: fixUrl(publicPath + '/.*'),
65 handler: 'cacheFirst'
66 },
67 // Cache other routes if offline
68 {
69 urlPattern: fixUrl(routerBase + '/.*'),
70 handler: 'networkFirst'
71 }
72 ],
73 runtimeCaching: []
74 }
75
76 const options = defaultsDeep({}, this.options.workbox, moduleOptions, defaults)
77
78 return options
79}
80
81// =============================================
82// addTemplates
83// =============================================
84
85function addTemplates (options) {
86 // Add sw.template.js
87 this.addTemplate({
88 src: path.resolve(__dirname, 'templates/sw.template.js'),
89 fileName: 'sw.template.js',
90 options: {
91 importScripts: [options.wbDst].concat(options.importScripts || []),
92 runtimeCaching: [].concat(options._runtimeCaching, options.runtimeCaching).map(i => (Object.assign({}, i, {
93 urlPattern: i.urlPattern,
94 handler: i.handler || 'networkFirst',
95 method: i.method || 'GET'
96 }))),
97 wbOptions: {
98 cacheId: options.cacheId,
99 clientsClaim: options.clientsClaim,
100 directoryIndex: options.directoryIndex
101 }
102 }
103 })
104
105 // Add sw.plugin.js
106 if (options.autoRegister) {
107 const swURL = `${options.routerBase}/${options.swURL || 'sw.js'}`
108 this.addPlugin({
109 src: path.resolve(__dirname, 'templates/sw.plugin.js'),
110 ssr: false,
111 fileName: 'sw.plugin.js',
112 options: {
113 swURL: fixUrl(swURL),
114 swScope: fixUrl(`${options.routerBase}/`)
115 }
116 })
117 }
118}
119
120// =============================================
121// emitAssets
122// =============================================
123
124function emitAssets (options) {
125 const assets = []
126 const emitAsset = (path, name, ext = 'js') => {
127 const source = readFileSync(path)
128 const hash = hashSum(source)
129 const dst = `${name}.${hash}.${ext}`
130 assets.push({source, dst})
131 return dst
132 }
133
134 // Write assets after build
135 const hook = builder => {
136 assets.forEach(({source, dst}) => {
137 writeFileSync(path.resolve(this.options.buildDir, 'dist', dst), source, 'utf-8')
138 })
139 }
140
141 if (this.nuxt.hook) {
142 this.nuxt.hook('build:done', hook)
143 } else {
144 this.nuxt.plugin('build', builder => {
145 builder.plugin('built', hook)
146 })
147 }
148
149 // workbox.js
150 let wbPath = require.resolve('workbox-sw')
151 if (options.dev) {
152 wbPath = wbPath.replace(/prod/g, 'dev')
153 }
154 options.wbDst = fixUrl(options.publicPath + '/' + emitAsset(wbPath, 'workbox' + (options.dev ? '.dev' : '')))
155}
156
157// =============================================
158// workboxInject
159// =============================================
160
161function workboxInject (options) {
162 const hook = () => {
163 const opts = Object.assign({}, options)
164 delete opts.runtimeCaching
165 return swBuild.injectManifest(opts)
166 }
167
168 if (this.nuxt.hook) {
169 this.nuxt.hook('build:done', hook)
170 } else {
171 this.nuxt.plugin('build', builder => {
172 builder.plugin('built', hook)
173 })
174 }
175}
176
177module.exports.meta = require('./package.json')