-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
/
ModernModePlugin.js
113 lines (102 loc) · 4.04 KB
/
ModernModePlugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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 }) {
this.targetDir = targetDir
this.isModernBuild = isModernBuild
this.unsafeInline = unsafeInline
}
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 = legacyAssets[0].attributes.src
.split('/')
.slice(0, -1)
.concat(['safari-nomodule-fix.js'])
.join('/')
compilation.assets[safariFixPath] = {
source: function () {
return new Buffer(safariFix)
},
size: function () {
return Buffer.byteLength(safariFix)
}
}
data.body.push({
tagName: 'script',
closeTag: true,
attributes: {
src: safariFixPath
}
})
}
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