1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const { SyncWaterfallHook } = require("tapable");
|
8 | const Compilation = require("../Compilation");
|
9 | const RuntimeGlobals = require("../RuntimeGlobals");
|
10 | const Template = require("../Template");
|
11 | const HelperRuntimeModule = require("./HelperRuntimeModule");
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | const compilationHooksMap = new WeakMap();
|
23 |
|
24 | class LoadScriptRuntimeModule extends HelperRuntimeModule {
|
25 | |
26 |
|
27 |
|
28 |
|
29 | static getCompilationHooks(compilation) {
|
30 | if (!(compilation instanceof Compilation)) {
|
31 | throw new TypeError(
|
32 | "The 'compilation' argument must be an instance of Compilation"
|
33 | );
|
34 | }
|
35 | let hooks = compilationHooksMap.get(compilation);
|
36 | if (hooks === undefined) {
|
37 | hooks = {
|
38 | createScript: new SyncWaterfallHook(["source", "chunk"])
|
39 | };
|
40 | compilationHooksMap.set(compilation, hooks);
|
41 | }
|
42 | return hooks;
|
43 | }
|
44 |
|
45 | |
46 |
|
47 |
|
48 | constructor(withCreateScriptUrl) {
|
49 | super("load script");
|
50 | this._withCreateScriptUrl = withCreateScriptUrl;
|
51 | }
|
52 |
|
53 | |
54 |
|
55 |
|
56 | generate() {
|
57 | const { compilation } = this;
|
58 | const { runtimeTemplate, outputOptions } = compilation;
|
59 | const {
|
60 | scriptType,
|
61 | chunkLoadTimeout: loadTimeout,
|
62 | crossOriginLoading,
|
63 | uniqueName,
|
64 | charset
|
65 | } = outputOptions;
|
66 | const fn = RuntimeGlobals.loadScript;
|
67 |
|
68 | const { createScript } =
|
69 | LoadScriptRuntimeModule.getCompilationHooks(compilation);
|
70 |
|
71 | const code = Template.asString([
|
72 | "script = document.createElement('script');",
|
73 | scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "",
|
74 | charset ? "script.charset = 'utf-8';" : "",
|
75 | `script.timeout = ${loadTimeout / 1000};`,
|
76 | `if (${RuntimeGlobals.scriptNonce}) {`,
|
77 | Template.indent(
|
78 | `script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
|
79 | ),
|
80 | "}",
|
81 | uniqueName
|
82 | ? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);'
|
83 | : "",
|
84 | `script.src = ${
|
85 | this._withCreateScriptUrl
|
86 | ? `${RuntimeGlobals.createScriptUrl}(url)`
|
87 | : "url"
|
88 | };`,
|
89 | crossOriginLoading
|
90 | ? Template.asString([
|
91 | "if (script.src.indexOf(window.location.origin + '/') !== 0) {",
|
92 | Template.indent(
|
93 | `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
|
94 | ),
|
95 | "}"
|
96 | ])
|
97 | : ""
|
98 | ]);
|
99 |
|
100 | return Template.asString([
|
101 | "var inProgress = {};",
|
102 | uniqueName
|
103 | ? `var dataWebpackPrefix = ${JSON.stringify(uniqueName + ":")};`
|
104 | : "// data-webpack is not used as build has no uniqueName",
|
105 | "// loadScript function to load a script via script tag",
|
106 | `${fn} = ${runtimeTemplate.basicFunction("url, done, key, chunkId", [
|
107 | "if(inProgress[url]) { inProgress[url].push(done); return; }",
|
108 | "var script, needAttach;",
|
109 | "if(key !== undefined) {",
|
110 | Template.indent([
|
111 | 'var scripts = document.getElementsByTagName("script");',
|
112 | "for(var i = 0; i < scripts.length; i++) {",
|
113 | Template.indent([
|
114 | "var s = scripts[i];",
|
115 | `if(s.getAttribute("src") == url${
|
116 | uniqueName
|
117 | ? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key'
|
118 | : ""
|
119 | }) { script = s; break; }`
|
120 | ]),
|
121 | "}"
|
122 | ]),
|
123 | "}",
|
124 | "if(!script) {",
|
125 | Template.indent([
|
126 | "needAttach = true;",
|
127 | createScript.call(code, this.chunk)
|
128 | ]),
|
129 | "}",
|
130 | "inProgress[url] = [done];",
|
131 | "var onScriptComplete = " +
|
132 | runtimeTemplate.basicFunction(
|
133 | "prev, event",
|
134 | Template.asString([
|
135 | "// avoid mem leaks in IE.",
|
136 | "script.onerror = script.onload = null;",
|
137 | "clearTimeout(timeout);",
|
138 | "var doneFns = inProgress[url];",
|
139 | "delete inProgress[url];",
|
140 | "script.parentNode && script.parentNode.removeChild(script);",
|
141 | `doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
|
142 | "fn(event)",
|
143 | "fn"
|
144 | )});`,
|
145 | "if(prev) return prev(event);"
|
146 | ])
|
147 | ),
|
148 | ";",
|
149 | `var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`,
|
150 | "script.onerror = onScriptComplete.bind(null, script.onerror);",
|
151 | "script.onload = onScriptComplete.bind(null, script.onload);",
|
152 | "needAttach && document.head.appendChild(script);"
|
153 | ])};`
|
154 | ]);
|
155 | }
|
156 | }
|
157 |
|
158 | module.exports = LoadScriptRuntimeModule;
|