-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate manifest from original. Better export. #59
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"printWidth": 100, | ||
"trailingComma": "all", | ||
"singleQuote": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,24 @@ | ||
const { readFile, writeFile } = require('fs-extra'); | ||
const Precache = require('./next-files'); | ||
const { generateSW, injectManifest } = require('workbox-build'); | ||
const { join, resolve } = require('path'); | ||
const parseArgs = require('minimist'); | ||
|
||
const dev = process.env.NODE_ENV !== 'production'; | ||
|
||
module.exports = Export; | ||
|
||
async function Export(nextConfig) { | ||
const { | ||
distDir = '.next', | ||
exportPathMap, | ||
generateSw = true, | ||
workboxOpts = { | ||
runtimeCaching: [{ urlPattern: /^https?.*/, handler: 'networkFirst' }] | ||
const { copy } = require('fs-extra'); | ||
const { join } = require('path'); | ||
|
||
/** | ||
* Copy the generated service worker into the export folder. | ||
*/ | ||
function exportSw(nextConfig) { | ||
return async function exportPathMap(...args) { | ||
const [defaultPathMap, { dev, distDir, outDir }] = args; | ||
const swDest = nextConfig.workboxOpts.swDest || 'service-worker.js'; | ||
|
||
if (dev) { | ||
return defaultPathMap; | ||
} | ||
} = nextConfig; | ||
|
||
if (dev || typeof exportPathMap !== 'function') { | ||
return {}; | ||
} | ||
|
||
// Logic for working out dir and outdir copied from `next-export`: | ||
// https://github.com/zeit/next.js/blob/15dde33794622919d20709da97fa412a01831807/bin/next-export | ||
const argv = parseArgs(process.argv.slice(2), { | ||
alias: { o: 'outdir' }, | ||
default: { o: null } | ||
}); | ||
const dir = argv._[0] || '.'; | ||
const outDir = argv.outdir ? resolve(argv.outdir) : resolve(dir, 'out'); | ||
|
||
const nextDir = join(process.cwd(), dir, distDir); | ||
const buildIdPath = join(nextDir, 'BUILD_ID'); | ||
const buildId = await readFile(buildIdPath, 'utf8'); | ||
|
||
const { precaches } = await Precache({ buildId, nextDir }); | ||
// Copy service worker from Next.js build dir into the export dir. | ||
await copy(join(distDir, swDest), join(outDir, swDest)); | ||
|
||
const swDest = join(outDir, 'service-worker.js'); | ||
|
||
if (generateSw) { | ||
// globDirectory is intentionally left blank as it's required by workbox | ||
await generateSW({ swDest, globDirectory: ' ', ...workboxOpts }); | ||
} else { | ||
// So that the same file works as part of `next build` and `next export` | ||
const injectionPointRegexp = /(__precacheManifest\s*=\s*)\[\](.*)/; | ||
await injectManifest({ | ||
swDest, | ||
globDirectory: ' ', | ||
injectionPointRegexp, | ||
...workboxOpts | ||
}); | ||
} | ||
|
||
const serviceWorkerContent = await readFile(swDest, 'utf8'); | ||
const newServiceWorkerContent = `self.__precacheManifest = ${JSON.stringify( | ||
precaches | ||
)};\n${serviceWorkerContent}`; | ||
|
||
writeFile(swDest, newServiceWorkerContent); | ||
|
||
return nextConfig.exportPathMap(); | ||
// Run user's exportPathMap function if available. | ||
return nextConfig.exportPathMap ? nextConfig.exportPathMap(...args) : defaultPathMap; | ||
}; | ||
} | ||
|
||
module.exports = exportSw; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
const glob = require('glob'); | ||
const { readFile, writeFile } = require('fs-extra'); | ||
const { join } = require('path'); | ||
|
||
const nextUrlPrefix = '/_next/'; | ||
const excludeFiles = ['react-loadable-manifest.json', 'build-manifest.json']; | ||
|
||
/** | ||
* Workbox already generates a pretty good precache manifest for all the emitted | ||
* assets. There are a few things different though: | ||
* - there are emitted files that are not used and don't need to be cached | ||
* - all emitted assets need to be prefixed with /_next/ | ||
* - we don't need the revision as all the precacheable files are versioned | ||
* | ||
* At the end replace old manifest reference with new inlined one. | ||
*/ | ||
async function generateNextManifest(options) { | ||
const manifestFilePath = join(options.outputPath, options.manifestDest); | ||
const swFilePath = join(options.outputPath, options.swDest); | ||
|
||
const originalManifest = await getOriginalManifest(manifestFilePath); | ||
const nextManifest = buildNextManifest(originalManifest, options.assetPrefix); | ||
await inlineManifest(nextManifest, swFilePath, options.manifestDest); | ||
} | ||
|
||
function getOriginalManifest(manifestFilePath) { | ||
return new Promise((resolve, reject) => { | ||
glob(manifestFilePath, async (err, files = []) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
|
||
// Pick first and only as we've clean old ones. | ||
const file = await readFile(files[0], 'utf-8'); | ||
// Execute file with a self variable in the scope/context. | ||
const self = {}; | ||
new Function('self', file)(self); | ||
|
||
resolve(self.__precacheManifest); | ||
}); | ||
}); | ||
} | ||
|
||
function buildNextManifest(originalManifest, urlPrefix = '') { | ||
return originalManifest.filter(entry => !excludeFiles.includes(entry.url)).map(entry => ({ | ||
url: `${urlPrefix}${nextUrlPrefix}${entry.url}`, | ||
})); | ||
} | ||
|
||
async function inlineManifest(manifest, swFilePath, precachePath) { | ||
const originalSw = await readFile(swFilePath, 'utf8'); | ||
|
||
// Prepend/inline newly generated precache manifest and remove import for old one. | ||
const manifestImportRegex = new RegExp(`(,\s*)?"${precachePath}"`); | ||
const newSw = `self.__precacheManifest = ${JSON.stringify( | ||
manifest, | ||
null, | ||
2, | ||
)};\n\n${originalSw.replace(manifestImportRegex, '')}`; | ||
|
||
await writeFile(swFilePath, newSw, 'utf8'); | ||
} | ||
|
||
module.exports = generateNextManifest; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,9 @@ | |
"authors": [ | ||
"Jack Hanford" | ||
], | ||
"contributors": [ | ||
"Joao Vieira <joaoguerravieira@gmail.com>" | ||
], | ||
"tags": [ | ||
"react", | ||
"next.js", | ||
|
@@ -36,21 +39,20 @@ | |
"sw-helpers" | ||
], | ||
"peerDependencies": { | ||
"next": "^5.0.0 || ^6.0.0 || ^7.0.0" | ||
"next": ">=7.0.0" | ||
}, | ||
"dependencies": { | ||
"clean-webpack-plugin": "^0.1.19", | ||
"copy-webpack-plugin": "~4.5.2", | ||
"fs-extra": "~5.0.0", | ||
"glob": "~7.0.0", | ||
"minimist": "1.2.0", | ||
"webpack": "~3.11.0", | ||
"workbox-build": "^3.5.0", | ||
"workbox-webpack-plugin": "^3.5.0" | ||
"fs-extra": "~7.0.0", | ||
"glob": "~7.1.3", | ||
"webpack": "^4.19.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still required by workbox plugin even though it's not a peer dependency 😩 |
||
"workbox-webpack-plugin": "^3.6.1" | ||
}, | ||
"devDependencies": { | ||
"husky": "^0.14.3", | ||
"prettier": "^1.14.2", | ||
"pretty-quick": "^1.6.0" | ||
"prettier": "^1.14.3", | ||
"pretty-quick": "^1.7.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove unused ones and updated all packages |
||
}, | ||
"husky": { | ||
"hooks": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,19 @@ | ||
const fs = require('fs-extra'); | ||
const { join } = require('path'); | ||
const nextFiles = require('./next-files.js'); | ||
const generateNextManifest = require('./next-manifest'); | ||
|
||
module.exports = class NextFilePrecacherPlugin { | ||
module.exports = class InlineNextPrecacheManifestPlugin { | ||
constructor(opts) { | ||
this.opts = { | ||
filename: 'service-worker.js', | ||
...opts | ||
}; | ||
this.opts = opts; | ||
} | ||
|
||
apply(compiler) { | ||
compiler.plugin('after-emit', (compilation, callback) => { | ||
this.opts.filename = join( | ||
compiler.options.output.path, | ||
this.opts.filename | ||
); | ||
this.opts.outputPath = compiler.options.output.path; | ||
|
||
callback(); | ||
}); | ||
|
||
compiler.plugin( | ||
'done', | ||
async () => { | ||
const sw = await fs.readFile( | ||
join(this.opts.outputPath, 'service-worker.js'), | ||
'utf8' | ||
); | ||
const newPrecacheManifest = await nextFiles({ | ||
buildId: this.opts.buildId, | ||
nextDir: this.opts.outputPath, | ||
assetPrefix: this.opts.assetPrefix | ||
}); | ||
|
||
// Prepend/inline newly generated precache manifest and remove import for old one. | ||
const manifestImportRegex = /(,\s*)?"precache-manifest\..*\.js"/; | ||
const newSw = `self.__precacheManifest = ${JSON.stringify( | ||
newPrecacheManifest, | ||
null, | ||
2 | ||
)};\n\n${sw.replace(manifestImportRegex, '')}`; | ||
|
||
return await fs.writeFile(this.opts.filename, newSw, 'utf8'); | ||
await generateNextManifest(this.opts); | ||
}, | ||
err => { | ||
throw new Error(`Precached failed: ${err.toString()}`); | ||
} | ||
}, | ||
); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put some defaults that I use personally. the only difference I think was the
trailingComma
option. what do you prefer?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing makes sense