added WebAssembly Proof of Concept

This commit is contained in:
Tobias Koppers 2017-10-30 13:56:57 +01:00
parent b7c746d73f
commit 41a1d602e1
37 changed files with 1092 additions and 110 deletions

View File

@ -0,0 +1,507 @@
This very simple example shows usage of WebAssembly.
WebAssembly modules can be imported like other modules. Their download and compilation happens in parallel to the download and evaluation of the javascript chunk.
# example.js
``` javascript
import("./add.wasm").then(addModule => {
console.log(addModule.add(22, 2200));
import("./math").then(math => {
console.log(math.add(10, 101));
console.log(math.factorial(15));
console.log(math.factorialJavascript(15));
console.log(math.fibonacci(15));
console.log(math.fibonacciJavascript(15));
timed("wasm factorial", () => math.factorial(1500));
timed("js factorial", () => math.factorialJavascript(1500));
timed("wasm fibonacci", () => math.fibonacci(22));
timed("js fibonacci", () => math.fibonacciJavascript(22));
});
});
function timed(name, fn) {
if(!console.time || !console.timeEnd)
return fn();
// warmup
for(var i = 0; i < 10; i++)
fn();
console.time(name)
for(var i = 0; i < 5000; i++)
fn();
console.timeEnd(name)
}
```
# math.js
``` javascript
import { add } from "./add.wasm";
import { factorial } from "./factorial.wasm";
import { fibonacci } from "./fibonacci.wasm";
export { add, factorial, fibonacci };
export function factorialJavascript(i) {
if(i < 1) return 1;
return i * factorialJavascript(i - 1);
}
export function fibonacciJavascript(i) {
if(i < 2) return 1;
return fibonacciJavascript(i - 1) + fibonacciJavascript(i - 2);
}
```
# js/output.js
<details><summary><code>/******/ (function(modules) { /* webpackBootstrap */ })</code></summary>
``` javascript
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0], moreModules = data[1], executeModules = data[2];
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 2: 0
/******/ };
/******/
/******/ var scheduledModules = [];
/******/
/******/ // object to store loaded and loading wasm modules
/******/ var installedWasmModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId) {
/******/ var promises = [];
/******/
/******/
/******/ // JSONP chunk loading for javascript
/******/
/******/ var installedChunkData = installedChunks[chunkId];
/******/ if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/ // a Promise means "currently loading".
/******/ if(installedChunkData) {
/******/ promises.push(installedChunkData[2]);
/******/ } else {
/******/ // setup Promise in chunk cache
/******/ var promise = new Promise(function(resolve, reject) {
/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/ installedChunkData[2] = promise;
/******/ });
/******/
/******/ // start chunk loading
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.charset = 'utf-8';
/******/ script.timeout = 120000;
/******/
/******/ if (__webpack_require__.nc) {
/******/ script.setAttribute("nonce", __webpack_require__.nc);
/******/ }
/******/ script.src = __webpack_require__.p + "" + chunkId + ".output.js";
/******/ var timeout = setTimeout(function(){
/******/ onScriptComplete({ type: 'timeout', target: script });
/******/ }, 120000);
/******/ script.onerror = script.onload = onScriptComplete;
/******/ function onScriptComplete(event) {
/******/ // avoid mem leaks in IE.
/******/ script.onerror = script.onload = null;
/******/ clearTimeout(timeout);
/******/ var chunk = installedChunks[chunkId];
/******/ if(chunk !== 0) {
/******/ if(chunk) {
/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/ var realSrc = event && event.target && event.target.src;
/******/ var error = new Error('Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')');
/******/ error.type = errorType;
/******/ error.request = realSrc;
/******/ chunk[1](error);
/******/ }
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
/******/ head.appendChild(script);
/******/ promises.push(promise);
/******/ }
/******/ }
/******/
/******/ // Fetch + compile chunk loading for webassembly
/******/
/******/ var wasmModules = {"0":[1,3,4],"1":[1]}[chunkId] || [];
/******/
/******/ wasmModules.forEach(function(wasmModuleId) {
/******/ var installedWasmModuleData = installedWasmModules[wasmModuleId];
/******/
/******/ // a Promise means "currently loading" or "already loaded".
/******/ if(installedWasmModuleData) {
/******/ promises.push(installedWasmModuleData);
/******/ } else {
/******/ var promise = installedWasmModules[wasmModuleId] = fetch(__webpack_require__.p + "" + {"1":"80925f35a6f1cf550d38","3":"419044e1f2fdca0c3e4a","4":"353120d3a2f1efbb00e2"}[wasmModuleId] + ".wasm")
/******/ .then(function(response) { return response.arrayBuffer(); })
/******/ .then(function(bytes) { return WebAssembly.compile(bytes); })
/******/ .then(function(module) { __webpack_require__.w[wasmModuleId] = module; })
/******/ promises.push(promise);
/******/ }
/******/ });
/******/ return Promise.all(promises);
/******/ };
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "js/";
/******/
/******/ // on error function for async loading
/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/
/******/ // object with all compiled WebAssmbly.Modules
/******/ __webpack_require__.w = {};
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var parentJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
```
</details>
``` javascript
/******/ ([
/* 0 */
/*!********************!*\
!*** ./example.js ***!
\********************/
/*! no static exports found */
/*! all exports used */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__.e/* import() */(1).then(__webpack_require__.bind(null, /*! ./add.wasm */1)).then(addModule => {
console.log(addModule.add(22, 2200));
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, /*! ./math */2)).then(math => {
console.log(math.add(10, 101));
console.log(math.factorial(15));
console.log(math.factorialJavascript(15));
console.log(math.fibonacci(15));
console.log(math.fibonacciJavascript(15));
timed("wasm factorial", () => math.factorial(1500));
timed("js factorial", () => math.factorialJavascript(1500));
timed("wasm fibonacci", () => math.fibonacci(22));
timed("js fibonacci", () => math.fibonacciJavascript(22));
});
});
function timed(name, fn) {
if(!console.time || !console.timeEnd)
return fn();
// warmup
for(var i = 0; i < 10; i++)
fn();
console.time(name)
for(var i = 0; i < 5000; i++)
fn();
console.timeEnd(name)
}
/***/ })
/******/ ]);
```
# js/0.output.js
``` javascript
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0,1],[
/* 0 */,
/* 1 */
/*!******************!*\
!*** ./add.wasm ***!
\******************/
/*! no static exports found */
/*! all exports used */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Instanciate WebAssembly module
var instance = new WebAssembly.Instance(__webpack_require__.w[module.i], {});
// export exports from WebAssmbly module
module.exports = instance.exports;
/***/ }),
/* 2 */
/*!*****************!*\
!*** ./math.js ***!
\*****************/
/*! exports provided: add, factorial, fibonacci, factorialJavascript, fibonacciJavascript */
/*! all exports used */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "factorialJavascript", function() { return factorialJavascript; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fibonacciJavascript", function() { return fibonacciJavascript; });
/* harmony import */ var _add_wasm__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./add.wasm */1);
/* harmony import */ var _add_wasm__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_add_wasm__WEBPACK_IMPORTED_MODULE_0__);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "add", function() { return _add_wasm__WEBPACK_IMPORTED_MODULE_0__["add"]; });
/* harmony import */ var _factorial_wasm__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./factorial.wasm */3);
/* harmony import */ var _factorial_wasm__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_factorial_wasm__WEBPACK_IMPORTED_MODULE_1__);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "factorial", function() { return _factorial_wasm__WEBPACK_IMPORTED_MODULE_1__["factorial"]; });
/* harmony import */ var _fibonacci_wasm__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./fibonacci.wasm */4);
/* harmony import */ var _fibonacci_wasm__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_fibonacci_wasm__WEBPACK_IMPORTED_MODULE_2__);
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fibonacci", function() { return _fibonacci_wasm__WEBPACK_IMPORTED_MODULE_2__["fibonacci"]; });
function factorialJavascript(i) {
if(i < 1) return 1;
return i * factorialJavascript(i - 1);
}
function fibonacciJavascript(i) {
if(i < 2) return 1;
return fibonacciJavascript(i - 1) + fibonacciJavascript(i - 2);
}
/***/ }),
/* 3 */
/*!************************!*\
!*** ./factorial.wasm ***!
\************************/
/*! no static exports found */
/*! exports used: factorial */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Instanciate WebAssembly module
var instance = new WebAssembly.Instance(__webpack_require__.w[module.i], {});
// export exports from WebAssmbly module
module.exports = instance.exports;
/***/ }),
/* 4 */
/*!************************!*\
!*** ./fibonacci.wasm ***!
\************************/
/*! no static exports found */
/*! exports used: fibonacci */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Instanciate WebAssembly module
var instance = new WebAssembly.Instance(__webpack_require__.w[module.i], {});
// export exports from WebAssmbly module
module.exports = instance.exports;
/***/ })
]]);
```
# js/1.output.js
``` javascript
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/*!******************!*\
!*** ./add.wasm ***!
\******************/
/*! no static exports found */
/*! all exports used */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
// Instanciate WebAssembly module
var instance = new WebAssembly.Instance(__webpack_require__.w[module.i], {});
// export exports from WebAssmbly module
module.exports = instance.exports;
/***/ })
]]);
```
# Info
## Uncompressed
```
Hash: 0878b6979a580265faba
Version: webpack 3.8.1
Asset Size Chunks Chunk Names
0.output.js 3.55 kB 0, 1 [emitted]
80925f35a6f1cf550d38.wasm 41 bytes 0, 1, 1 [emitted]
419044e1f2fdca0c3e4a.wasm 83 bytes 0, 1 [emitted]
353120d3a2f1efbb00e2.wasm 95 bytes 0, 1 [emitted]
1.output.js 486 bytes 1 [emitted]
output.js 8.9 kB 2 [emitted] main
Entrypoint main = output.js
chunk {0} 0.output.js, 80925f35a6f1cf550d38.wasm, 419044e1f2fdca0c3e4a.wasm, 353120d3a2f1efbb00e2.wasm 634 bytes {2} [rendered]
> [0] ./example.js 3:1-17
[1] ./add.wasm 41 bytes {0} {1} [built]
import() ./add.wasm [0] ./example.js 1:0-20
harmony side effect evaluation ./add.wasm [2] ./math.js 1:0-33
harmony export imported specifier ./add.wasm [2] ./math.js 5:0-37
[2] ./math.js 415 bytes {0} [built]
[exports: add, factorial, fibonacci, factorialJavascript, fibonacciJavascript]
import() ./math [0] ./example.js 3:1-17
[3] ./factorial.wasm 83 bytes {0} [built]
[only some exports used: factorial]
harmony side effect evaluation ./factorial.wasm [2] ./math.js 2:0-45
harmony export imported specifier ./factorial.wasm [2] ./math.js 5:0-37
[4] ./fibonacci.wasm 95 bytes {0} [built]
[only some exports used: fibonacci]
harmony side effect evaluation ./fibonacci.wasm [2] ./math.js 3:0-45
harmony export imported specifier ./fibonacci.wasm [2] ./math.js 5:0-37
chunk {1} 1.output.js, 80925f35a6f1cf550d38.wasm 41 bytes {2} [rendered]
> [0] ./example.js 1:0-20
[1] ./add.wasm 41 bytes {0} {1} [built]
import() ./add.wasm [0] ./example.js 1:0-20
harmony side effect evaluation ./add.wasm [2] ./math.js 1:0-33
harmony export imported specifier ./add.wasm [2] ./math.js 5:0-37
chunk {2} output.js (main) 788 bytes [entry] [rendered]
> main [0] ./example.js
[0] ./example.js 788 bytes {2} [built]
```
## Minimized (uglify-js, no zip)
```
Hash: 0878b6979a580265faba
Version: webpack 3.8.1
Asset Size Chunks Chunk Names
0.output.js 772 bytes 0, 1 [emitted]
80925f35a6f1cf550d38.wasm 41 bytes 0, 1, 1 [emitted]
419044e1f2fdca0c3e4a.wasm 83 bytes 0, 1 [emitted]
353120d3a2f1efbb00e2.wasm 95 bytes 0, 1 [emitted]
1.output.js 155 bytes 1 [emitted]
output.js 8.74 kB 2 [emitted] main
Entrypoint main = output.js
chunk {0} 0.output.js, 80925f35a6f1cf550d38.wasm, 419044e1f2fdca0c3e4a.wasm, 353120d3a2f1efbb00e2.wasm 634 bytes {2} [rendered]
> [0] ./example.js 3:1-17
[1] ./add.wasm 41 bytes {0} {1} [built]
import() ./add.wasm [0] ./example.js 1:0-20
harmony side effect evaluation ./add.wasm [2] ./math.js 1:0-33
harmony export imported specifier ./add.wasm [2] ./math.js 5:0-37
[2] ./math.js 415 bytes {0} [built]
[exports: add, factorial, fibonacci, factorialJavascript, fibonacciJavascript]
import() ./math [0] ./example.js 3:1-17
[3] ./factorial.wasm 83 bytes {0} [built]
[only some exports used: factorial]
harmony side effect evaluation ./factorial.wasm [2] ./math.js 2:0-45
harmony export imported specifier ./factorial.wasm [2] ./math.js 5:0-37
[4] ./fibonacci.wasm 95 bytes {0} [built]
[only some exports used: fibonacci]
harmony side effect evaluation ./fibonacci.wasm [2] ./math.js 3:0-45
harmony export imported specifier ./fibonacci.wasm [2] ./math.js 5:0-37
chunk {1} 1.output.js, 80925f35a6f1cf550d38.wasm 41 bytes {2} [rendered]
> [0] ./example.js 1:0-20
[1] ./add.wasm 41 bytes {0} {1} [built]
import() ./add.wasm [0] ./example.js 1:0-20
harmony side effect evaluation ./add.wasm [2] ./math.js 1:0-33
harmony export imported specifier ./add.wasm [2] ./math.js 5:0-37
chunk {2} output.js (main) 788 bytes [entry] [rendered]
> main [0] ./example.js
[0] ./example.js 788 bytes {2} [built]
ERROR in output.js from UglifyJs
Unexpected token: operator (>) [output.js:194,95]
```

Binary file not shown.

View File

@ -0,0 +1 @@
require("../build-common");

View File

@ -0,0 +1,26 @@
import("./add.wasm").then(addModule => {
console.log(addModule.add(22, 2200));
import("./math").then(math => {
console.log(math.add(10, 101));
console.log(math.factorial(15));
console.log(math.factorialJavascript(15));
console.log(math.fibonacci(15));
console.log(math.fibonacciJavascript(15));
timed("wasm factorial", () => math.factorial(1500));
timed("js factorial", () => math.factorialJavascript(1500));
timed("wasm fibonacci", () => math.fibonacci(22));
timed("js fibonacci", () => math.fibonacciJavascript(22));
});
});
function timed(name, fn) {
if(!console.time || !console.timeEnd)
return fn();
// warmup
for(var i = 0; i < 10; i++)
fn();
console.time(name)
for(var i = 0; i < 5000; i++)
fn();
console.timeEnd(name)
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
<html>
<body>
<script src="js/output.js"></script>
</body>
</html>

View File

@ -0,0 +1,15 @@
import { add } from "./add.wasm";
import { factorial } from "./factorial.wasm";
import { fibonacci } from "./fibonacci.wasm";
export { add, factorial, fibonacci };
export function factorialJavascript(i) {
if(i < 1) return 1;
return i * factorialJavascript(i - 1);
}
export function fibonacciJavascript(i) {
if(i < 2) return 1;
return fibonacciJavascript(i - 1) + fibonacciJavascript(i - 2);
}

View File

@ -0,0 +1,47 @@
This very simple example shows usage of WebAssembly.
WebAssembly modules can be imported like other modules. Their download and compilation happens in parallel to the download and evaluation of the javascript chunk.
# example.js
``` javascript
{{example.js}}
```
# math.js
``` javascript
{{math.js}}
```
# js/output.js
``` javascript
{{js/output.js}}
```
# js/0.output.js
``` javascript
{{js/0.output.js}}
```
# js/1.output.js
``` javascript
{{js/1.output.js}}
```
# Info
## Uncompressed
```
{{stdout}}
```
## Minimized (uglify-js, no zip)
```
{{min:stdout}}
```

View File

@ -0,0 +1,14 @@
module.exports = {
output: {
webassemblyModuleFilename: "[modulehash].wasm",
publicPath: "js/"
},
module: {
rules: [
{
test: /\.wasm$/,
type: "webassembly/experimental"
}
]
}
};

View File

@ -490,7 +490,7 @@ class Chunk {
hash.update(`${this.id} `);
hash.update(this.ids ? this.ids.join(",") : "");
hash.update(`${this.name || ""} `);
this._modules.forEach(m => m.updateHash(hash));
this._modules.forEach(m => hash.update(m.hash));
}
canBeIntegrated(otherChunk) {
@ -538,12 +538,12 @@ class Chunk {
}
getChunkMaps(includeEntries, realHash) {
const chunksProcessed = [];
const chunkHashMap = {};
const chunkNameMap = {};
const chunksProcessed = new Set();
const chunkHashMap = Object.create(null);
const chunkNameMap = Object.create(null);
const addChunk = chunk => {
if(chunksProcessed.indexOf(chunk) >= 0) return;
chunksProcessed.push(chunk);
if(chunksProcessed.has(chunk)) return;
chunksProcessed.add(chunk);
if(!chunk.hasRuntime() || includeEntries) {
chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
if(chunk.name)
@ -558,6 +558,29 @@ class Chunk {
};
}
getChunkModuleMaps(includeEntries, filterFn) {
const chunksProcessed = new Set();
const chunkModuleIdMap = Object.create(null);
const chunkModuleHashMap = Object.create(null);
(function addChunk(chunk) {
if(chunksProcessed.has(chunk)) return;
chunksProcessed.add(chunk);
if(!chunk.hasRuntime() || includeEntries) {
const array = chunk.getModules().filter(filterFn);
if(array.length > 0)
chunkModuleIdMap[chunk.id] = array.map(m => m.id).sort();
for(const m of array) {
chunkModuleHashMap[m.id] = m.renderedHash;
}
}
chunk._chunks.forEach(addChunk);
}(this));
return {
id: chunkModuleIdMap,
hash: chunkModuleHashMap
};
}
sortModules(sortByFn) {
this._modules.sortWith(sortByFn || sortById);
}

View File

@ -12,8 +12,53 @@ module.exports = class ChunkTemplate extends Template {
super(outputOptions);
}
render(chunk, moduleTemplate, dependencyTemplates) {
const moduleSources = this.renderChunkModules(chunk, moduleTemplate, dependencyTemplates);
getRenderManifest(options) {
const chunk = options.chunk;
const outputOptions = options.outputOptions;
const moduleTemplates = options.moduleTemplates;
const dependencyTemplates = options.dependencyTemplates;
const result = [];
let filenameTemplate;
if(chunk.isInitial()) {
if(chunk.filenameTemplate)
filenameTemplate = chunk.filenameTemplate;
else
filenameTemplate = outputOptions.filename;
} else {
filenameTemplate = outputOptions.chunkFilename;
}
result.push({
render: () => this.renderJavascript(chunk, moduleTemplates.javascript, dependencyTemplates),
filenameTemplate,
pathOptions: {
chunk
},
identifier: `chunk${chunk.id}`,
hash: chunk.hash
});
for(const module of chunk.modules.filter(m => m.type && m.type.startsWith("webassembly"))) {
const filenameTemplate = outputOptions.webassemblyModuleFilename;
result.push({
render: () => this.renderWebAssembly(module, moduleTemplates.webassembly, dependencyTemplates),
filenameTemplate,
pathOptions: {
module
},
identifier: `webassemblyModule${module.id}`,
hash: module.hash
});
}
return result;
}
renderJavascript(chunk, moduleTemplate, dependencyTemplates) {
const moduleSources = this.renderChunkModules(chunk, m => true, moduleTemplate, dependencyTemplates);
const core = this.applyPluginsWaterfall("modules", moduleSources, chunk, moduleTemplate, dependencyTemplates);
let source = this.applyPluginsWaterfall("render", core, chunk, moduleTemplate, dependencyTemplates);
if(chunk.hasEntryModule()) {
@ -23,6 +68,10 @@ module.exports = class ChunkTemplate extends Template {
return new ConcatSource(source, ";");
}
renderWebAssembly(module, moduleTemplate, dependencyTemplates) {
return moduleTemplate.render(module, dependencyTemplates);
}
updateHash(hash) {
hash.update("ChunkTemplate");
hash.update("2");

View File

@ -70,7 +70,10 @@ class Compilation extends Tapable {
this.mainTemplate = new MainTemplate(this.outputOptions);
this.chunkTemplate = new ChunkTemplate(this.outputOptions);
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(this.outputOptions);
this.moduleTemplate = new ModuleTemplate(this.outputOptions);
this.moduleTemplates = {
javascript: new ModuleTemplate(this.outputOptions),
webassembly: new ModuleTemplate(this.outputOptions)
};
this.semaphore = new Semaphore(options.parallelism || 100);
@ -99,6 +102,15 @@ class Compilation extends Tapable {
this._rebuildingModules = new Map();
}
// TODO add deprecation getter/setters
get moduleTemplate() {
return this.moduleTemplates.javascript;
}
set moduleTemplate(value) {
this.moduleTemplate.javascript = value;
}
getStats() {
return new Stats(this);
}
@ -1346,6 +1358,14 @@ class Compilation extends Tapable {
this.children.forEach(child => hash.update(child.hash));
this.warnings.forEach(warning => hash.update(`${warning.message}`));
this.errors.forEach(error => hash.update(`${error.message}`));
const modules = this.modules;
for(let i = 0; i < modules.length; i++) {
const module = modules[i];
const moduleHash = crypto.createHash(hashFunction);
module.updateHash(moduleHash);
module.hash = moduleHash.digest(hashDigest);
module.renderedHash = module.hash.substr(0, hashDigestLength);
}
// clone needed as sort below is inplace mutation
const chunks = this.chunks.slice();
/**
@ -1407,45 +1427,44 @@ class Compilation extends Tapable {
createChunkAssets() {
const outputOptions = this.outputOptions;
const filename = outputOptions.filename;
const chunkFilename = outputOptions.chunkFilename;
for(let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
chunk.files = [];
const chunkHash = chunk.hash;
let source;
let file;
const filenameTemplate = chunk.filenameTemplate ? chunk.filenameTemplate :
chunk.isInitial() ? filename :
chunkFilename;
let filenameTemplate;
try {
const useChunkHash = !chunk.hasRuntime() || (this.mainTemplate.useChunkHash && this.mainTemplate.useChunkHash(chunk));
const usedHash = useChunkHash ? chunkHash : this.fullHash;
const cacheName = "c" + chunk.id;
if(this.cache && this.cache[cacheName] && this.cache[cacheName].hash === usedHash) {
source = this.cache[cacheName].source;
} else {
if(chunk.hasRuntime()) {
source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);
const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate;
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
}); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
for(const fileManifest of manifest) {
const cacheName = fileManifest.identifier;
const usedHash = fileManifest.hash;
filenameTemplate = fileManifest.filenameTemplate;
if(this.cache && this.cache[cacheName] && this.cache[cacheName].hash === usedHash) {
source = this.cache[cacheName].source;
} else {
source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);
}
if(this.cache) {
this.cache[cacheName] = {
hash: usedHash,
source: source = (source instanceof CachedSource ? source : new CachedSource(source))
};
source = fileManifest.render();
if(this.cache) {
this.cache[cacheName] = {
hash: usedHash,
source: source = (source instanceof CachedSource ? source : new CachedSource(source))
};
}
}
file = this.getPath(filenameTemplate, fileManifest.pathOptions);
if(this.assets[file] && this.assets[file] !== source)
throw new Error(`Conflict: Multiple assets emit to the same filename ${file}`);
this.assets[file] = source;
chunk.files.push(file);
this.applyPlugins2("chunk-asset", chunk, file);
}
file = this.getPath(filenameTemplate, {
noChunkHash: !useChunkHash,
chunk
});
if(this.assets[file])
throw new Error(`Conflict: Multiple assets emit to the same filename ${file}`);
this.assets[file] = source;
chunk.files.push(file);
this.applyPlugins2("chunk-asset", chunk, file);
} catch(err) {
this.errors.push(new ChunkRenderError(chunk, file || filenameTemplate, err));
}

View File

@ -18,7 +18,7 @@ class ContextModule extends Module {
// resolveDependencies: (fs: FS, options: ContextOptions, (err: Error?, dependencies: Dependency[]) => void) => void
// options: ContextOptions
constructor(resolveDependencies, options) {
super();
super("javascript/dynamic");
// Info from Factory
this.resolveDependencies = resolveDependencies;

View File

@ -13,7 +13,7 @@ const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDepen
class DelegatedModule extends Module {
constructor(sourceRequest, data, type, userRequest, originalRequest) {
super();
super("javascript/dynamic");
// Info from Factory
this.sourceRequest = sourceRequest;

View File

@ -9,7 +9,7 @@ const RawSource = require("webpack-sources").RawSource;
class DllModule extends Module {
constructor(context, dependencies, name, type) {
super();
super("javascript/dynamic");
// Info from Factory
this.context = context;

View File

@ -11,7 +11,7 @@ const Template = require("./Template");
class ExternalModule extends Module {
constructor(request, type, userRequest) {
super();
super("javascript/dynamic");
// Info from Factory
this.request = request;

View File

@ -0,0 +1,87 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
class FetchCompileWasmMainTemplatePlugin {
apply(mainTemplate) {
mainTemplate.plugin("local-vars", function(source, chunk) {
return this.asString([
source,
"",
"// object to store loaded and loading wasm modules",
"var installedWasmModules = {};",
]);
});
mainTemplate.plugin("require-ensure", function(source, chunk, hash) {
const webassemblyModuleFilename = this.outputOptions.webassemblyModuleFilename;
const chunkModuleMaps = chunk.getChunkModuleMaps(false, m => m.type.startsWith("webassembly"));
if(Object.keys(chunkModuleMaps.id).length === 0) return source;
const wasmModuleSrcPath = this.applyPluginsWaterfall("asset-path", JSON.stringify(webassemblyModuleFilename), {
hash: `" + ${this.renderCurrentHashCode(hash)} + "`,
hashWithLength: length => `" + ${this.renderCurrentHashCode(hash, length)} + "`,
module: {
id: "\" + wasmModuleId + \"",
hash: `" + ${JSON.stringify(chunkModuleMaps.hash)}[wasmModuleId] + "`,
hashWithLength(length) {
const shortChunkHashMap = Object.create(null);
Object.keys(chunkModuleMaps.hash).forEach(wasmModuleId => {
if(typeof chunkModuleMaps.hash[wasmModuleId] === "string")
shortChunkHashMap[wasmModuleId] = chunkModuleMaps.hash[wasmModuleId].substr(0, length);
});
return `" + ${JSON.stringify(shortChunkHashMap)}[wasmModuleId] + "`;
}
}
});
return this.asString([
source,
"",
"// Fetch + compile chunk loading for webassembly",
"",
`var wasmModules = ${JSON.stringify(chunkModuleMaps.id)}[chunkId] || [];`,
"",
"wasmModules.forEach(function(wasmModuleId) {",
this.indent([
"var installedWasmModuleData = installedWasmModules[wasmModuleId];",
"",
"// a Promise means \"currently loading\" or \"already loaded\".",
"if(installedWasmModuleData) {",
this.indent([
"promises.push(installedWasmModuleData);"
]),
"} else {",
this.indent([
`var promise = installedWasmModules[wasmModuleId] = fetch(${this.requireFn}.p + ${wasmModuleSrcPath})`,
this.indent([
".then(function(response) { return response.arrayBuffer(); })",
".then(function(bytes) { return WebAssembly.compile(bytes); })",
`.then(function(module) { ${this.requireFn}.w[wasmModuleId] = module; })`
]),
"promises.push(promise);"
]),
"}",
]),
"});",
]);
});
mainTemplate.plugin("require-extensions", function(source, chunk) {
return this.asString([
source,
"",
"// object with all compiled WebAssmbly.Modules",
`${this.requireFn}.w = {};`
]);
});
mainTemplate.plugin("hash", function(hash) {
hash.update("jsonp");
hash.update("5");
hash.update(`${this.outputOptions.filename}`);
hash.update(`${this.outputOptions.chunkFilename}`);
hash.update(`${this.outputOptions.jsonpFunction}`);
hash.update(`${this.outputOptions.hotUpdateFunction}`);
});
}
}
module.exports = FetchCompileWasmMainTemplatePlugin;

View File

@ -0,0 +1,37 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RawSource = require("webpack-sources").RawSource;
class FetchCompileWasmModuleTemplatePlugin {
apply(moduleTemplate) {
moduleTemplate.plugin("module", function(moduleSource, module, chunk) {
if(module.type && module.type.startsWith("webassembly")) {
if(chunk.isInitial())
throw new Error("Sync WebAsssmbly compilation is not yet implemented");
const source = new RawSource([
"\"use strict\";",
"",
"// Instanciate WebAssembly module",
"var instance = new WebAssembly.Instance(__webpack_require__.w[module.i], {});",
"",
"// export exports from WebAssmbly module",
// TODO rewrite this to getters depending on exports to support circular dependencies
"module.exports = instance.exports;"
].join("\n"));
return source;
} else {
return moduleSource;
}
});
moduleTemplate.plugin("hash", function(hash) {
hash.update("FetchCompileWasmModuleTemplatePlugin");
hash.update("1");
});
}
}
module.exports = FetchCompileWasmModuleTemplatePlugin;

View File

@ -0,0 +1,19 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const FetchCompileWasmMainTemplatePlugin = require("./FetchCompileWasmMainTemplatePlugin");
const FetchCompileWasmModuleTemplatePlugin = require("./FetchCompileWasmModuleTemplatePlugin");
class FetchCompileWasmTemplatePlugin {
apply(compiler) {
compiler.plugin("this-compilation", (compilation) => {
compilation.mainTemplate.apply(new FetchCompileWasmMainTemplatePlugin());
compilation.moduleTemplates.javascript.apply(new FetchCompileWasmModuleTemplatePlugin());
});
}
}
module.exports = FetchCompileWasmTemplatePlugin;

View File

@ -12,7 +12,8 @@ class FunctionModuleTemplatePlugin {
moduleTemplate.plugin("render", (moduleSource, module) => {
const source = new ConcatSource();
const defaultArguments = [module.moduleArgument || "module", module.exportsArgument || "exports"];
if((module.arguments && module.arguments.length !== 0) || module.hasDependencies(d => d.requireWebpackRequire !== false)) {
// TODO remove HACK checking type for javascript
if(!module.type || !module.type.startsWith("javascript") || (module.arguments && module.arguments.length !== 0) || module.hasDependencies(d => d.requireWebpackRequire !== false)) {
defaultArguments.push("__webpack_require__");
}
source.add("/***/ (function(" + defaultArguments.concat(module.arguments || []).join(", ") + ") {\n\n");

View File

@ -17,7 +17,7 @@ module.exports = class HotUpdateChunkTemplate extends Template {
hotUpdateChunk.id = id;
hotUpdateChunk.setModules(modules);
hotUpdateChunk.removedModules = removedModules;
const modulesSource = this.renderChunkModules(hotUpdateChunk, moduleTemplate, dependencyTemplates);
const modulesSource = this.renderChunkModules(hotUpdateChunk, () => true, moduleTemplate, dependencyTemplates);
const core = this.applyPluginsWaterfall("modules", modulesSource, modules, removedModules, moduleTemplate, dependencyTemplates);
const source = this.applyPluginsWaterfall("render", core, modules, removedModules, hash, id, moduleTemplate, dependencyTemplates);
return source;

View File

@ -19,7 +19,7 @@ class JsonpMainTemplatePlugin {
return mainTemplate.asString([
source,
"",
"// objects to store loaded and loading chunks",
"// object to store loaded and loading chunks",
"var installedChunks = {",
mainTemplate.indent(
chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")
@ -93,36 +93,40 @@ class JsonpMainTemplatePlugin {
"};",
]);
});
mainTemplate.plugin("require-ensure", (_, chunk, hash) => {
mainTemplate.plugin("require-ensure", (source, chunk, hash) => {
return mainTemplate.asString([
source,
"",
"// JSONP chunk loading for javascript",
"",
"var installedChunkData = installedChunks[chunkId];",
"if(installedChunkData === 0) {",
"if(installedChunkData !== 0) { // 0 means \"already installed\".",
mainTemplate.indent([
"return new Promise(function(resolve) { resolve(); });"
"",
"// a Promise means \"currently loading\".",
"if(installedChunkData) {",
mainTemplate.indent([
"promises.push(installedChunkData[2]);"
]),
"} else {",
mainTemplate.indent([
"// setup Promise in chunk cache",
"var promise = new Promise(function(resolve, reject) {",
mainTemplate.indent([
"installedChunkData = installedChunks[chunkId] = [resolve, reject];"
]),
"installedChunkData[2] = promise;",
"});",
"",
"// start chunk loading",
"var head = document.getElementsByTagName('head')[0];",
mainTemplate.applyPluginsWaterfall("jsonp-script", "", chunk, hash),
"head.appendChild(script);",
"promises.push(promise);"
]),
"}",
]),
"}",
"",
"// a Promise means \"currently loading\".",
"if(installedChunkData) {",
mainTemplate.indent([
"return installedChunkData[2];"
]),
"}",
"",
"// setup Promise in chunk cache",
"var promise = new Promise(function(resolve, reject) {",
mainTemplate.indent([
"installedChunkData = installedChunks[chunkId] = [resolve, reject];"
]),
"});",
"installedChunkData[2] = promise;",
"",
"// start chunk loading",
"var head = document.getElementsByTagName('head')[0];",
mainTemplate.applyPluginsWaterfall("jsonp-script", "", chunk, hash),
"head.appendChild(script);",
"",
"return promise;"
]);
});
mainTemplate.plugin("require-extensions", (source, chunk) => {

View File

@ -20,6 +20,7 @@ const Template = require("./Template");
// __webpack_require__.o = Object.prototype.hasOwnProperty.call
// __webpack_require__.n = compatibility get default export
// __webpack_require__.h = the webpack hash
// __webpack_require__.w = an object containing all installed WebAssembly.Modules keys by module id
// __webpack_require__.oe = the uncatched error handler for the webpack runtime
// __webpack_require__.nc = the script nonce
@ -41,7 +42,7 @@ module.exports = class MainTemplate extends Template {
source.add("/******/ })\n");
source.add("/************************************************************************/\n");
source.add("/******/ (");
const modules = this.renderChunkModules(chunk, moduleTemplate, dependencyTemplates, "/******/ ");
const modules = this.renderChunkModules(chunk, () => true, moduleTemplate, dependencyTemplates, "/******/ ");
source.add(this.applyPluginsWaterfall("modules", modules, chunk, hash, moduleTemplate, dependencyTemplates));
source.add(")");
return source;
@ -103,7 +104,9 @@ module.exports = class MainTemplate extends Template {
buf.push("// This file contains only the entry chunk.");
buf.push("// The chunk loading function for additional chunks");
buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
buf.push(this.indent(this.applyPluginsWaterfall("require-ensure", "throw new Error('Not chunk loading available');", chunk, hash, "chunkId")));
buf.push(this.indent("var promises = [];"));
buf.push(this.indent(this.applyPluginsWaterfall("require-ensure", "", chunk, hash, "chunkId")));
buf.push(this.indent("return Promise.all(promises);"));
buf.push("};");
}
buf.push("");
@ -162,6 +165,41 @@ module.exports = class MainTemplate extends Template {
this.requireFn = "__webpack_require__";
}
getRenderManifest(options) {
const chunk = options.chunk;
const hash = options.hash;
const fullHash = options.fullHash;
const outputOptions = options.outputOptions;
const moduleTemplates = options.moduleTemplates;
const dependencyTemplates = options.dependencyTemplates;
const result = [];
let filenameTemplate;
if(chunk.filenameTemplate)
filenameTemplate = chunk.filenameTemplate;
else if(chunk.isInitial())
filenameTemplate = outputOptions.filename;
else {
filenameTemplate = outputOptions.chunkFilename;
}
const useChunkHash = this.useChunkHash(chunk);
result.push({
render: () => this.render(hash, chunk, moduleTemplates.javascript, dependencyTemplates),
filenameTemplate,
pathOptions: {
noChunkHash: !useChunkHash,
chunk
},
identifier: `chunk${chunk.id}`,
hash: useChunkHash ? chunk.hash : fullHash
});
return result;
}
render(hash, chunk, moduleTemplate, dependencyTemplates) {
const buf = [];
buf.push(this.applyPluginsWaterfall("bootstrap", "", chunk, hash, moduleTemplate, dependencyTemplates));

View File

@ -42,8 +42,9 @@ const getDebugIdent = set => {
class Module extends DependenciesBlock {
constructor() {
constructor(type) {
super();
this.type = type;
// Unique Id
this.debugId = debugId++;

View File

@ -10,12 +10,15 @@ module.exports = class ModuleTemplate extends Template {
constructor(outputOptions) {
super(outputOptions);
}
// TODO move chunk into extra options object, it's not available i. e. in wasm modules
render(module, dependencyTemplates, chunk) {
const moduleSource = module.source(dependencyTemplates, this.outputOptions, this.requestShortener);
const moduleSourcePostModule = this.applyPluginsWaterfall("module", moduleSource, module, chunk, dependencyTemplates);
const moduleSourcePostRender = this.applyPluginsWaterfall("render", moduleSourcePostModule, module, chunk, dependencyTemplates);
return this.applyPluginsWaterfall("package", moduleSourcePostRender, module, chunk, dependencyTemplates);
}
updateHash(hash) {
hash.update("1");
this.applyPlugins("hash", hash);

View File

@ -11,7 +11,7 @@ const RawSource = require("webpack-sources").RawSource;
class MultiModule extends Module {
constructor(context, dependencies, name) {
super();
super("javascript/dynamic");
// Info from Factory
this.context = context;

View File

@ -32,6 +32,13 @@ const asString = (buf) => {
return buf;
};
const asBuffer = str => {
if(!Buffer.isBuffer(str)) {
return new Buffer(str, "utf-8"); // eslint-disable-line
}
return str;
};
const contextify = (context, request) => {
return request.split("!").map(r => {
const splitPath = r.split("?");
@ -59,13 +66,14 @@ const dependencyTemplatesHashMap = new WeakMap();
class NormalModule extends Module {
constructor(request, userRequest, rawRequest, loaders, resource, parser) {
super();
constructor(type, request, userRequest, rawRequest, loaders, resource, parser) {
super(type);
// Info from Factory
this.request = request;
this.userRequest = userRequest;
this.rawRequest = rawRequest;
this.binary = type.startsWith("webassembly");
this.parser = parser;
this.resource = resource;
this.context = getContext(resource);
@ -177,6 +185,10 @@ class NormalModule extends Module {
return new SourceMapSource(source, identifier, sourceMap);
}
if(Buffer.isBuffer(source)) {
return new RawSource(source);
}
return new OriginalSource(source, identifier);
}
@ -211,7 +223,7 @@ class NormalModule extends Module {
return callback(error);
}
this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
this._source = this.createSource(this.binary ? asBuffer(source) : asString(source), resourceBuffer, sourceMap);
this._ast = typeof extraInfo === "object" && extraInfo !== null && extraInfo.webpackAST !== undefined ? extraInfo.webpackAST : null;
return callback();
});
@ -473,23 +485,26 @@ class NormalModule extends Module {
}
source(dependencyTemplates, outputOptions, requestShortener) {
const hashDigest = this.getHashDigest(dependencyTemplates);
if(this._cachedSource && this._cachedSource.hash === hashDigest) {
return this._cachedSource.source;
if(this.type.startsWith("javascript")) {
const hashDigest = this.getHashDigest(dependencyTemplates);
if(this._cachedSource && this._cachedSource.hash === hashDigest) {
return this._cachedSource.source;
}
if(!this._source) {
return new RawSource("throw new Error('No source available');");
}
const source = new ReplaceSource(this._source);
this._cachedSource = {
source: source,
hash: hashDigest
};
this.sourceBlock(this, [], dependencyTemplates, source, outputOptions, requestShortener);
return new CachedSource(source);
}
if(!this._source) {
return new RawSource("throw new Error('No source available');");
}
const source = new ReplaceSource(this._source);
this._cachedSource = {
source: source,
hash: hashDigest
};
this.sourceBlock(this, [], dependencyTemplates, source, outputOptions, requestShortener);
return new CachedSource(source);
return this._source;
}
originalSource() {

View File

@ -9,6 +9,7 @@ const Tapable = require("tapable");
const NormalModule = require("./NormalModule");
const RawModule = require("./RawModule");
const Parser = require("./Parser");
const WebAssemblyParser = require("./WebAssemblyParser");
const RuleSet = require("./RuleSet");
const loaderToIdent = data => {
@ -80,6 +81,7 @@ class NormalModuleFactory extends Tapable {
}
createdModule = new NormalModule(
result.type,
result.request,
result.userRequest,
result.rawRequest,
@ -191,6 +193,7 @@ class NormalModuleFactory extends Tapable {
if(err) return callback(err);
loaders = results[0].concat(loaders, results[1], results[2]);
process.nextTick(() => {
const type = settings.type || "javascript/auto";
callback(null, {
context: context,
request: loaders.map(loaderToIdent).concat([resource]).join("!"),
@ -200,7 +203,8 @@ class NormalModuleFactory extends Tapable {
loaders,
resource,
resourceResolveData,
parser: this.getParser(settings.parser)
type,
parser: this.getParser(type, settings.parser)
});
});
});
@ -268,23 +272,39 @@ class NormalModuleFactory extends Tapable {
}, callback);
}
getParser(parserOptions) {
let ident = "null";
getParser(type, parserOptions) {
let ident = type;
if(parserOptions) {
if(parserOptions.ident)
ident = parserOptions.ident;
ident = `${type}|${parserOptions.ident}`;
else
ident = JSON.stringify(parserOptions);
ident = JSON.stringify([type, parserOptions]);
}
const parser = this.parserCache[ident];
if(parser)
return parser;
return this.parserCache[ident] = this.createParser(parserOptions);
return this.parserCache[ident] = this.createParser(type, parserOptions);
}
createParser(parserOptions) {
const parser = new Parser();
this.applyPlugins2("parser", parser, parserOptions || {});
createParser(type, parserOptions) {
// TODO move to plugin
// const parser = this.applyPluginsBailResult1(`create-parser ${type}`);
let parser;
switch(type) {
case "javascript/auto":
parser = new Parser();
break;
case "webassembly/experimental":
parser = new WebAssemblyParser();
break;
default:
throw new Error("Unknown module type");
}
if(type === "javascript/auto") {
// TODO: remove backward compat in webpack 5
this.applyPlugins2("parser", parser, parserOptions || {});
}
this.applyPlugins2(`parser ${type}`, parser, parserOptions || {});
return parser;
}
}

View File

@ -11,7 +11,7 @@ const RawSource = require("webpack-sources").RawSource;
module.exports = class RawModule extends Module {
constructor(source, identifier, readableIdentifier) {
super();
super("javascript/dynamic");
this.sourceStr = source;
this.identifierStr = identifier || this.sourceStr;
this.readableIdentifierStr = readableIdentifier || this.identifierStr;

View File

@ -126,15 +126,16 @@ module.exports = class Template extends Tapable {
return arrayOverhead < objectOverhead ? [minId, maxId] : false;
}
renderChunkModules(chunk, moduleTemplate, dependencyTemplates, prefix) {
renderChunkModules(chunk, filterFn, moduleTemplate, dependencyTemplates, prefix) {
if(!prefix) prefix = "";
var source = new ConcatSource();
if(chunk.getNumberOfModules() === 0) {
const modules = chunk.getModules().filter(filterFn);
var removedModules = chunk.removedModules;
if(modules.length === 0 && removedModules.length === 0) {
source.add("[]");
return source;
}
var removedModules = chunk.removedModules;
var allModules = chunk.mapModules(module => {
var allModules = modules.map(module => {
return {
id: module.id,
source: moduleTemplate.render(module, dependencyTemplates, chunk)
@ -156,7 +157,7 @@ module.exports = class Template extends Tapable {
var maxId = bounds[1];
if(minId !== 0) source.add("Array(" + minId + ").concat(");
source.add("[\n");
var modules = {};
const modules = {};
allModules.forEach(module => {
modules[module.id] = module;
});

View File

@ -6,8 +6,10 @@
const REGEXP_HASH = /\[hash(?::(\d+))?\]/gi,
REGEXP_CHUNKHASH = /\[chunkhash(?::(\d+))?\]/gi,
REGEXP_MODULEHASH = /\[modulehash(?::(\d+))?\]/gi,
REGEXP_NAME = /\[name\]/gi,
REGEXP_ID = /\[id\]/gi,
REGEXP_MODULEID = /\[moduleid\]/gi,
REGEXP_FILE = /\[file\]/gi,
REGEXP_QUERY = /\[query\]/gi,
REGEXP_FILEBASE = /\[filebase\]/gi;
@ -50,6 +52,10 @@ const replacePathVariables = (path, data) => {
const chunkName = chunk && (chunk.name || chunk.id);
const chunkHash = chunk && (chunk.renderedHash || chunk.hash);
const chunkHashWithLength = chunk && chunk.hashWithLength;
const module = data.module;
const moduleId = module && module.id;
const moduleHash = module && (module.renderedHash || module.hash);
const moduleHashWithLength = module && module.hashWithLength;
if(data.noChunkHash && REGEXP_CHUNKHASH_FOR_TEST.test(path)) {
throw new Error(`Cannot use [chunkhash] for chunk in '${path}' (use [hash] instead)`);
@ -58,7 +64,9 @@ const replacePathVariables = (path, data) => {
return path
.replace(REGEXP_HASH, withHashLength(getReplacer(data.hash), data.hashWithLength))
.replace(REGEXP_CHUNKHASH, withHashLength(getReplacer(chunkHash), chunkHashWithLength))
.replace(REGEXP_MODULEHASH, withHashLength(getReplacer(moduleHash), moduleHashWithLength))
.replace(REGEXP_ID, getReplacer(chunkId))
.replace(REGEXP_MODULEID, getReplacer(moduleId))
.replace(REGEXP_NAME, getReplacer(chunkName))
.replace(REGEXP_FILE, getReplacer(data.filename))
.replace(REGEXP_FILEBASE, getReplacer(data.basename))

25
lib/WebAssemblyParser.js Normal file
View File

@ -0,0 +1,25 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
const Tapable = require("tapable");
class WebAssemblyParser extends Tapable {
constructor(options) {
super();
this.options = options;
}
parse(source, initialState) {
// Does nothing current
// TODO parse WASM AST and walk it
// extract exports, imports
return initialState;
}
}
module.exports = WebAssemblyParser;

View File

@ -62,6 +62,7 @@ class WebpackOptionsApply extends OptionsApply {
compiler.dependencies = options.dependencies;
if(typeof options.target === "string") {
let JsonpTemplatePlugin;
let FetchCompileWasmTemplatePlugin;
let NodeSourcePlugin;
let NodeTargetPlugin;
let NodeTemplatePlugin;
@ -69,9 +70,11 @@ class WebpackOptionsApply extends OptionsApply {
switch(options.target) {
case "web":
JsonpTemplatePlugin = require("./JsonpTemplatePlugin");
FetchCompileWasmTemplatePlugin = require("./FetchCompileWasmTemplatePlugin");
NodeSourcePlugin = require("./node/NodeSourcePlugin");
compiler.apply(
new JsonpTemplatePlugin(options.output),
new FetchCompileWasmTemplatePlugin(options.output),
new FunctionModulePlugin(options.output),
new NodeSourcePlugin(options.node),
new LoaderTargetPlugin(options.target)

View File

@ -48,6 +48,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
const filename = options.output.filename;
return filename.indexOf("[name]") >= 0 ? filename.replace("[name]", "[id]") : "[id]." + filename;
});
this.set("output.webassemblyModuleFilename", "[modulehash].module.wasm");
this.set("output.library", "");
this.set("output.hotUpdateFunction", "make", (options) => {
return Template.toIdentifier("webpackHotUpdate" + options.output.library);

View File

@ -159,7 +159,7 @@ const getPathInAst = (ast, node) => {
class ConcatenatedModule extends Module {
constructor(rootModule, modules) {
super();
super("javascript/esm");
super.setChunks(rootModule._chunks);
// Info from Factory

View File

@ -260,6 +260,11 @@
"type": "string",
"absolutePath": false
},
"webassemblyModuleFilename": {
"description": "The filename of WebAssembly modules as relative path inside the `output.path` directory.",
"type": "string",
"absolutePath": false
},
"crossOriginLoading": {
"description": "This option enables cross-origin loading of chunks.",
"enum": [
@ -653,6 +658,14 @@
"query": {
"$ref": "#/definitions/ruleSet-query"
},
"type": {
"enum": [
"javascript/auto",
"javascript/dynamic",
"javascript/esm",
"webassembly/experimental"
]
},
"resource": {
"allOf": [
{