UNPKG

4.7 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const { SyncWaterfallHook } = require("tapable");
8const Compilation = require("../Compilation");
9const RuntimeGlobals = require("../RuntimeGlobals");
10const Template = require("../Template");
11const HelperRuntimeModule = require("./HelperRuntimeModule");
12
13/** @typedef {import("../Chunk")} Chunk */
14/** @typedef {import("../Compiler")} Compiler */
15
16/**
17 * @typedef {Object} LoadScriptCompilationHooks
18 * @property {SyncWaterfallHook<[string, Chunk]>} createScript
19 */
20
21/** @type {WeakMap<Compilation, LoadScriptCompilationHooks>} */
22const compilationHooksMap = new WeakMap();
23
24class LoadScriptRuntimeModule extends HelperRuntimeModule {
25 /**
26 * @param {Compilation} compilation the compilation
27 * @returns {LoadScriptCompilationHooks} hooks
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 * @param {boolean=} withCreateScriptUrl use create script url for trusted types
47 */
48 constructor(withCreateScriptUrl) {
49 super("load script");
50 this._withCreateScriptUrl = withCreateScriptUrl;
51 }
52
53 /**
54 * @returns {string} runtime code
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
158module.exports = LoadScriptRuntimeModule;