-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.js
More file actions
209 lines (190 loc) · 7.09 KB
/
index.js
File metadata and controls
209 lines (190 loc) · 7.09 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/**
* pub-webpack-plugin:
* 1. inject block webpackEmitedJs with emited js to pug template;
* 2. inject block webpackEmitedCss with emited css to pug template;
* 3. copy pug template and it's dependencies to build path
* 4. support require('*') in pug
* 5. pug hot reload
*/
const path = require('path')
const pug = require('pug');
const fs = require('fs');
const fsExtra = require('fs-extra')
const SingleEntryPlugin = require("webpack/lib/SingleEntryPlugin");
const vm = require("vm");
const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook;
const hooks = {
afterEmit: new AsyncSeriesWaterfallHook(['arg1'])
}
class PugWebpackPlugin {
constructor (options) {
this.options = options
this.name = 'PugWebpackPlugin'
}
apply (compiler) {
// callback is called when css,js is emited
compiler.hooks.emit.tapAsync(this.name, (compilation, callback) => {
// read pug template
const entryFileBuffer = fs.readFileSync(this.options.template)
// get pug dependences
const deps = pug.compileClientWithDependenciesTracked(entryFileBuffer, {
filename: this.options.template
}).dependencies
// copy pug dependences to /build for express view engine
deps.forEach(srcPath => {
const destPath = path.resolve(this.options.outputPath, path.relative(this.options.context, srcPath))
fsExtra.copySync(srcPath, destPath)
})
// add pug file dependencies to compilation
compilation.fileDependencies.add(this.options.template)
deps.forEach(dep => {
compilation.fileDependencies.add(dep)
})
// Get chunks info as json
// Note: we're excluding stuff that we don't need to improve toJson serialization speed.
const chunkOnlyConfig = {
assets: false,
cached: false,
children: false,
chunks: true,
chunkModules: false,
chunkOrigins: false,
errorDetails: false,
hash: false,
modules: false,
reasons: false,
source: false,
timings: false,
version: false
};
// get pug template content string
let content = entryFileBuffer.toString('utf8')
// inject emited css and js
let jsBlock = `
block webpackEmitedJs`
let cssBlock = `
block webpackEmitedCss`
const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks
const chunks = filterChunks(allChunks, this.options.includes, this.options.excludes)
chunks.forEach(chunk => {
chunk.files.forEach(name => {
if (/\.css$/.test(name)) { // append css link
cssBlock += `
link(rel="stylesheet" href="${this.options.publicPath + name}")`
} else { // append js script
jsBlock += `
script(src="${this.options.publicPath + name}")`
}
})
})
// append block webpackEmitedJs and block webpackEmitedCss to pug template content
content += `
${jsBlock}
${cssBlock}
`
/**
* Now we must handle require in pug template, let loader system do it for you.
*/
function randomIdent() {
return "xxxHTMLLINKxxx" + Math.random() + Math.random() + "xxx";
}
// find and replace require('*') with xxxHTMLLINKxxx*xxx placeholder
let requireCount = 0
content = content.replace(/require\(['|"]([^()]*)['|"]\)/g, (match, url) => {
requireCount++
// generate placeloader
const ident = randomIdent()
// resolve alias and get the full resource path
let resourcePath
const alias = (compiler.options.resolve || {}).alias
if (alias && url[0] !== '/' && url[0] !== '.') {
const k = url.substring(0, url.indexOf('/'))
resourcePath = alias[k] + url.substring(url.indexOf('/'), url.length)
} else {
resourcePath = path.join(this.options.template.substring(0, this.options.template.lastIndexOf('/')), url)
}
// create child compiler to handle require
const childCompiler = compilation.createChildCompiler(url, {
filename: url,
publicPath: this.options.publicPath
});
new SingleEntryPlugin(compilation.compiler.context, resourcePath).apply(childCompiler)
let source
childCompiler.plugin("after-compile", (compilation, callback) => {
source = compilation.assets[url] && compilation.assets[url].source();
// Remove all chunk assets
compilation.chunks.forEach(function(chunk) {
chunk.files.forEach(function(file) {
delete compilation.assets[file];
});
});
callback();
})
childCompiler.runAsChild((err, entries, childCompilation) => {
// add file dependencies for hmr
childCompilation.fileDependencies.forEach((dep) => {
compilation.fileDependencies.add(dep)
})
// run the emited js source, get hashed url or base64 content back and replace the placeholer with it
const hashedUrl = vm.runInThisContext(source)
content = content.replace(ident, _ => "'" + hashedUrl + "'")
if (requireCount) requireCount--
if (!requireCount) {
emitPugTemplate.call(this, content, callback)
}
});
return ident // replace require('*') with placeloader
})
if (!requireCount) {
emitPugTemplate.call(this, content, callback)
}
function emitPugTemplate (content, callback) {
const destPath = path.resolve(
this.options.outputPath,
path.relative(this.options.context, this.options.template)
)
fsExtra.outputFileSync(destPath, content)
callback()
// if (this.options.reloadPageFn) this.options.reloadPageFn()
hooks.afterEmit.promise({
plugin: this
}).catch(err => {
console.error(err);
return null;
}).then(() => null)
}
})
/**
* Return all chunks from the compilation result which match the exclude and include filters
*/
function filterChunks (chunks, includedChunks, excludedChunks) {
return chunks.filter(chunk => {
const chunkName = chunk.names[0];
// This chunk doesn't have a name. This script can't handled it.
if (chunkName === undefined) {
return false;
}
// Skip if the chunk should be lazy loaded
if (typeof chunk.isInitial === 'function') {
if (!chunk.isInitial()) {
return false;
}
} else if (!chunk.initial) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was not added explicity
if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) {
return false;
}
// Skip if the chunks should be filtered and the given chunk was excluded explicity
if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) {
return false;
}
// Add otherwise
return true;
});
}
}
}
PugWebpackPlugin.hooks = hooks
module.exports = PugWebpackPlugin