Created
June 28, 2018 18:56
-
-
Save ptb/a774ca170ae71c18adacd7c42ff4906f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint compat/compat: off, max-statements: off */ | |
const RawSource = require ("webpack-sources/lib/RawSource") | |
const evaluate = require ("eval") | |
const path = require ("path") | |
const cheerio = require ("cheerio") | |
const url = require ("url") | |
const Promise = require ("bluebird") | |
const findAsset = (src, compilation, webpackStatsJson) => { | |
if (!src) { | |
const chunkNames = Object.keys (webpackStatsJson.assetsByChunkName) | |
src = chunkNames[0] | |
} | |
const asset = compilation.assets[src] | |
if (asset) { | |
return asset | |
} | |
let chunkValue = webpackStatsJson.assetsByChunkName[src] | |
if (!chunkValue) { | |
return null | |
} | |
// Webpack outputs an array for each chunk when using sourcemaps | |
if (chunkValue instanceof Array) { | |
// Is the main bundle always the first element? | |
chunkValue = chunkValue[0] | |
} | |
return compilation.assets[chunkValue] | |
} | |
const getAssetsFromCompilation = (compilation, webpackStatsJson) => { | |
const assets = {} | |
for (const chunk in webpackStatsJson.assetsByChunkName) { | |
let chunkValue = webpackStatsJson.assetsByChunkName[chunk] | |
// Webpack outputs an array for each chunk when using sourcemaps | |
if (chunkValue instanceof Array) { | |
// Is the main bundle always the first element? | |
chunkValue = chunkValue[0] | |
} | |
if (compilation.options.output.publicPath) { | |
chunkValue = compilation.options.output.publicPath + chunkValue | |
} | |
assets[chunk] = chunkValue | |
} | |
return assets | |
} | |
const pathToAssetName = (outputPath) => { | |
let outputFileName = outputPath.replace (/^(\/|\\)/, "") | |
if (!(/\.(html?)$/i).test (outputFileName)) { | |
outputFileName = path.join (outputFileName, "index.html") | |
} | |
return outputFileName | |
} | |
const makeObject = (key, value) => { | |
const obj = {} | |
obj[key] = value | |
return obj | |
} | |
const relativePathsFromHtml = (options) => { | |
const html = options.source | |
const currentPath = options.path | |
const $ = cheerio.load (html) | |
const linkHrefs = $ ("a[href]") | |
.map (function (_, el) { | |
return $ (el).attr ("href") | |
}) | |
.get () | |
const iframeSrcs = $ ("iframe[src]") | |
.map (function (_, el) { | |
return $ (el).attr ("src") | |
}) | |
.get () | |
return [] | |
.concat (linkHrefs) | |
.concat (iframeSrcs) | |
.map (function (href) { | |
if (href.indexOf ("//") === 0) { | |
return null | |
} | |
const parsed = url.parse (href) | |
if (parsed.protocol || typeof parsed.path !== "string") { | |
return null | |
} | |
return parsed.path.indexOf ("/") === 0 | |
? parsed.path | |
: url.resolve (currentPath, parsed.path) | |
}) | |
.filter (function (href) { | |
return href !== null | |
}) | |
} | |
const renderPaths = ( | |
crawl, | |
userLocals, | |
paths, | |
render, | |
assets, | |
webpackStats, | |
compilation | |
) => { | |
const renderPromises = paths.map (function (outputPath) { | |
const locals = { | |
"assets": assets, | |
"path": outputPath, | |
"webpackStats": webpackStats | |
} | |
for (const prop in userLocals) { | |
if (userLocals.hasOwnProperty (prop)) { | |
locals[prop] = userLocals[prop] | |
} | |
} | |
const renderPromise = | |
render.length < 2 | |
? Promise.resolve (render (locals)) | |
: Promise.fromCallback (render.bind (null, locals)) | |
return renderPromise | |
.then (function (output) { | |
const outputByPath = | |
typeof output === "object" ? output : makeObject (outputPath, output) | |
const assetGenerationPromises = Object.keys (outputByPath).map ( | |
function (key) { | |
const rawSource = outputByPath[key] | |
const assetName = pathToAssetName (key) | |
if (compilation.assets[assetName]) { | |
return | |
} | |
compilation.assets[assetName] = new RawSource (rawSource) | |
if (crawl) { | |
const relativePaths = relativePathsFromHtml ({ | |
"path": key, | |
"source": rawSource | |
}) | |
return renderPaths ( | |
crawl, | |
userLocals, | |
relativePaths, | |
render, | |
assets, | |
webpackStats, | |
compilation | |
) | |
} | |
} | |
) | |
return Promise.all (assetGenerationPromises) | |
}) | |
.catch (function (err) { | |
compilation.errors.push (err.stack) | |
}) | |
}) | |
return Promise.all (renderPromises) | |
} | |
module.exports = class { | |
constructor (options = {}) { | |
this.entry = options.entry | |
this.paths = Array.isArray (options.paths) | |
? options.paths | |
: [options.paths || "/"] | |
this.locals = options.locals | |
this.globals = options.globals | |
this.crawl = Boolean (options.crawl) | |
} | |
apply (compiler) { | |
const self = this | |
compiler.hooks.thisCompilation.tap ( | |
"static-site-generator-webpack-plugin", | |
function (compilation) { | |
compilation.hooks.optimizeAssets.tapAsync ( | |
"static-site-generator-webpack-plugin", | |
function (_, done) { | |
const webpackStats = compilation.getStats () | |
const webpackStatsJson = webpackStats.toJson () | |
try { | |
const asset = findAsset ( | |
self.entry, | |
compilation, | |
webpackStatsJson | |
) | |
if (asset === null) { | |
throw new Error (`Source file not found: "${self.entry}"`) | |
} | |
const assets = getAssetsFromCompilation ( | |
compilation, | |
webpackStatsJson | |
) | |
const source = asset.source () | |
let render = evaluate ( | |
source, | |
self.entry, | |
self.globals, | |
true | |
) | |
if (render.hasOwnProperty ("default")) { | |
render = render.default | |
} | |
if (typeof render !== "function") { | |
throw new Error ( | |
`Export from "${ | |
self.entry | |
}" must be a function that returns an HTML string. Is output.libraryTarget in the configuration set to "umd"?` | |
) | |
} | |
renderPaths ( | |
self.crawl, | |
self.locals, | |
self.paths, | |
render, | |
assets, | |
webpackStats, | |
compilation | |
).nodeify (done) | |
} catch (err) { | |
compilation.errors.push (err.stack) | |
done () | |
} | |
} | |
) | |
} | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment