-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathplugin.js
135 lines (105 loc) · 3.52 KB
/
plugin.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import getTemplateRunner from './template';
const templateHandlersByLoaderCtx = new WeakMap();
const setTemplateHandler = (loaderCtx, handler)=> (
templateHandlersByLoaderCtx.set(loaderCtx, handler)
);
export const getTemplateHandler = (loaderCtx)=> (
templateHandlersByLoaderCtx.get(loaderCtx)
);
const loaderDataByModule = new WeakMap();
const setLoaderData = (module, data)=> loaderDataByModule.set(module, data);
const getLoaderData = (module)=> loaderDataByModule.get(module);
/**
* Return a `{scripts, styles}` object containing
* all chunks for a given `entry` separated
* into js scripts and css style sheets.
*/
const getFiles = (entry)=> {
const files = [];
for (const chunk of entry.chunks) {
files.push(...chunk.files);
}
const scripts = files.filter((fle)=> fle.endsWith('.js'));
const styles = files.filter((fle)=> fle.endsWith('.css'));
return {scripts, styles};
};
function* getDependencies(parentModule, processed=new Set()) {
if (!parentModule || processed.has(parentModule)) {
return;
}
processed.add(parentModule);
yield parentModule;
for (const {module} of parentModule.dependencies) {
// TODO: find test to deal with non-module dependencies
/* istanbul ignore else */
if (module) {
yield * getDependencies(module.rootModule || module, processed);
}
}
}
function* chunkEntryModules(compilation) {
for (const entry of compilation.entrypoints.values()) {
for (const {entryModule} of entry.chunks) {
yield [entry, entryModule];
}
}
}
function* findEntries(compilation) {
for (const [entry, entryModule] of chunkEntryModules(compilation)) {
for (const module of getDependencies(entryModule)) {
const data = getLoaderData(module);
if (data) {
yield [entry, module, data];
break;
}
}
}
}
/**
* Return a tapable hook that will add HTML assets to the `compilation`
* for every entry module that has a template generated by compatible loaders.
*/
const addHtmlAssets = (compilation)=> async ()=> {
const genHtml = getTemplateRunner(compilation);
for (const [entry, module, loaderData] of findEntries(compilation)) {
const {output, template, props} = loaderData;
const {scripts, styles} = getFiles(entry);
const {resource, context} = module;
const templateProps = {...props, scripts, styles};
const html = await genHtml(resource, context, template, templateProps);
compilation.assets[output] = {
source: ()=> html,
size: ()=> html.length
};
}
};
/**
* Register a callback on the `module` loader `context`.
*
* The callback allows any compatible loader to report a template back
* to this plugin.
*/
const registerModuleLoaderCallback = (loaderCtx, module)=> {
setTemplateHandler(loaderCtx, (data)=> setLoaderData(module, data));
};
/**
* Return a tapable hook for the plugin named `name` that registers
* hooks for the given `compilation` to generate HTML assets for
* templates set by any compatible loader e.g. `react-entry-loader`.
*/
const getCompilationHook = (name)=> (compilation)=> {
const {hooks} = compilation;
hooks.additionalAssets.tapPromise(name, addHtmlAssets(compilation));
hooks.normalModuleLoader.tap(name, registerModuleLoaderCallback);
};
/**
* A webpack plugin for generating HTML assets from templates sent by
* compatible webpack loaders.
*/
class EntryTransformPlugin {
apply(compiler) {
const name = this.constructor.name;
compiler.hooks.thisCompilation.tap(name, getCompilationHook(name));
}
}
export default EntryTransformPlugin;