const fs = require('fs-extra') const path = require('path') // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();` class ModernModePlugin { constructor ({ targetDir, isModernBuild, unsafeInline, jsDirectory }) { this.targetDir = targetDir this.isModernBuild = isModernBuild this.unsafeInline = unsafeInline this.jsDirectory = jsDirectory } apply (compiler) { if (!this.isModernBuild) { this.applyLegacy(compiler) } else { this.applyModern(compiler) } } applyLegacy (compiler) { const ID = `vue-cli-legacy-bundle` compiler.hooks.compilation.tap(ID, compilation => { compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => { // get stats, write to disk await fs.ensureDir(this.targetDir) const htmlName = path.basename(data.plugin.options.filename) // Watch out for output files in sub directories const htmlPath = path.dirname(data.plugin.options.filename) const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`) await fs.mkdirp(path.dirname(tempFilename)) await fs.writeFile(tempFilename, JSON.stringify(data.body)) cb() }) }) } applyModern (compiler) { const ID = `vue-cli-modern-bundle` compiler.hooks.compilation.tap(ID, compilation => { compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => { // use <script type="module"> for modern assets data.body.forEach(tag => { if (tag.tagName === 'script' && tag.attributes) { tag.attributes.type = 'module' } }) // use <link rel="modulepreload"> instead of <link rel="preload"> // for modern assets data.head.forEach(tag => { if (tag.tagName === 'link' && tag.attributes.rel === 'preload' && tag.attributes.as === 'script') { tag.attributes.rel = 'modulepreload' } }) // inject links for legacy assets as <script nomodule> const htmlName = path.basename(data.plugin.options.filename) // Watch out for output files in sub directories const htmlPath = path.dirname(data.plugin.options.filename) const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`) const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8')) .filter(a => a.tagName === 'script' && a.attributes) legacyAssets.forEach(a => { a.attributes.nomodule = '' }) if (this.unsafeInline) { // inject inline Safari 10 nomodule fix data.body.push({ tagName: 'script', closeTag: true, innerHTML: safariFix }) } else { // inject the fix as an external script const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js') const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath) compilation.assets[safariFixPath] = { source: function () { return new Buffer(safariFix) }, size: function () { return Buffer.byteLength(safariFix) } } data.body.push({ tagName: 'script', closeTag: true, attributes: { src: fullSafariFixPath } }) } data.body.push(...legacyAssets) await fs.remove(tempFilename) cb() }) compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => { data.html = data.html.replace(/\snomodule="">/g, ' nomodule>') }) }) } } ModernModePlugin.safariFix = safariFix module.exports = ModernModePlugin