为什么 webpack 无法加载块?
Why is webpack failing to load chunks?
我正在尝试在导入 emscripten 创建的 webassembly 库的 Typescript 项目上使用 webpack。这就是 Power BI 自定义视觉对象中的全部 运行,这让一切变得更加有趣。由于 Power BI 上下文、Typescript 和 WebAssembly,我认为这是额外的问题,但目前看来它可能只是一个 webpack 问题。
我认为是路径问题,但我是 webpack 的新手,有点迷茫。编译有效,但视觉对象为块 1 抛出一个 "ChunkLoadError"。(有两个块。)可能相关或不相关的事情:
- 我可以将浏览器定向到“https://localhost:8080/assets/1.js”(或...0.js)就好了。
- 我发现(我认为)因为 Power BI 将我的代码放在 iframe 中,最初 webpack 尝试的请求 url 是“https://app.powerbi.com/13.0.11428.218/assets/0.js". There was a baseUri property on the script tag it generated which caused this. Changing
output[publicPath]
to "https://localhost:8080/assets/” 允许浏览器下载块(我可以在浏览器的开发工具中看到它们成功进入),但 webpack 仍然报错。
- 尝试清除 node_modules 并重新安装。没有骰子。
在上述之后,错误如下所示:
VM1292:119 Uncaught (in promise) ChunkLoadError: Loading chunk 1 failed.
(missing: https://localhost:8080/assets/1.js)
at Function.requireEnsure [as e] (<anonymous>:119:26)
at new Visual (<anonymous>:23155:79)
at r.create [as creator] (<anonymous>:237:14)
at r.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:6200)
at https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:14488
at t.executeSafely (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:18033)
at t.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:14439)
at i.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:35819)
at i.executeMessage (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:42074)
at i.onMessageReceived (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:41772)
如果我暂停异常,我会在这里结束:
/******/ var error = new Error();
/******/ onScriptComplete = function (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;
/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
/******/ error.name = 'ChunkLoadError';
/******/ error.type = errorType;
/******/ error.request = realSrc;
/******/ ----> chunk[1](error);
/******/ }
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
"Paused on promise rejection"
但是,唉,我真的不知道我在看什么,或者为了什么。
这是我的 webpack.config.js:
const path = require("path");
const fs = require("fs");
const webpack = require("webpack");
console.log(require.resolve("powerbi-visuals-webpack-plugin"));
const PowerBICustomVisualsWebpackPlugin = require("powerbi-visuals-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const Visualizer = require("webpack-visualizer-plugin");
const ExtraWatchWebpackPlugin = require("extra-watch-webpack-plugin");
// api configuration
const powerbiApi = require("powerbi-visuals-api");
// visual configuration json path
const pbivizPath = "./pbiviz.json";
const pbivizFile = require(path.join(__dirname, pbivizPath));
// the visual capabilities content
const capabilitiesPath = "./capabilities.json";
const capabilitiesFile = require(path.join(__dirname, capabilitiesPath));
const pluginLocation = "./.tmp/precompile/visualPlugin.ts"; // path to visual plugin file, the file generates by the plugin
// string resources
// const resourcesFolder = path.join(".", "stringResources");
// const localizationFolders = fs.readdirSync(resourcesFolder);
const localizationFolders = [];
const resourcesFolder = ".";
// babel options to support IE11
const babelOptions = {
presets: [
[
require.resolve("@babel/preset-env"),
{
targets: {
ie: "11",
},
useBuiltIns: "entry",
modules: false,
corejs: "3",
},
],
],
sourceType: "unambiguous", // Tell babel that the project can contain different module types, not only es2015 modules.
cacheDirectory: path.join(".tmp", "babelCache"),
};
module.exports = {
entry: {
"visual.js": pluginLocation,
},
target: "web",
node: {
fs: "empty",
},
optimization: {
concatenateModules: false,
// minimize: true, // enable minimization for create *.pbiviz file less than 2 Mb, can be disabled for dev mode
},
devtool: "source-map",
mode: "development",
module: {
rules: [
{
parser: {
amd: false,
},
},
{
test: /(\.ts)x|\.ts$/,
include: /powerbi-visuals-|src|precompile\visualPlugin.ts/,
use: [
{
loader: require.resolve("babel-loader"),
options: babelOptions,
},
{
loader: require.resolve("ts-loader"),
options: {
transpileOnly: false,
experimentalWatchApi: false,
},
},
],
},
{
test: /(\.js)x|\.js$/,
use: [
{
loader: require.resolve("babel-loader"),
options: babelOptions,
},
],
},
{
test: /\.json$/,
loader: require.resolve("json-loader"),
type: "javascript/auto",
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: require.resolve("css-loader"),
},
{
loader: require.resolve("less-loader"),
options: {
paths: [path.resolve(__dirname, "..", "node_modules")],
},
},
],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: require.resolve("css-loader"),
},
],
},
{
test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp)$/i,
use: [
{
loader: "base64-inline-loader",
},
],
},
],
},
resolve: {
extensions: [".wasm", ".tsx", ".ts", ".jsx", ".js", ".css"],
},
output: {
path: path.join(__dirname, "/.tmp", "drop"),
publicPath: "https://localhost:8080/assets/",
chunkFilename: "[name].js",
filename: "[name]",
},
devServer: {
disableHostCheck: true,
contentBase: path.join(__dirname, ".tmp", "drop"), // path with assets for dev server, they are generated by webpack plugin
compress: true,
port: 8080, // dev server port
publicPath: "https://localhost:8080/assets/",
hot: false,
inline: false,
// cert files for dev server
https: {
pfx: fs.readFileSync(path.join(__dirname, "node_modules/powerbi-visuals-tools/certs/PowerBICustomVisualTest_public.pfx")), // for windows
passphrase: "##########",
},
headers: {
"access-control-allow-origin": "*",
"cache-control": "public, max-age=0",
},
},
externals: {
"powerbi-visuals-api": "null",
fakeDefine: "false",
corePowerbiObject: "Function('return this.powerbi')()",
realWindow: "Function('return this')()",
},
plugins: [
new MiniCssExtractPlugin({
filename: "visual.css",
chunkFilename: "[id].css",
}),
new Visualizer({
filename: "webpack.statistics.dev.html",
}),
// visual plugin regenerates with the visual source, but it does not require relaunching dev server
new webpack.WatchIgnorePlugin([
path.join(__dirname, pluginLocation),
"./.tmp/**/*.*",
]),
// custom visuals plugin instance with options
new PowerBICustomVisualsWebpackPlugin({
...pbivizFile,
capabilities: capabilitiesFile,
stringResources: localizationFolders.map((localization) => path.join(
resourcesFolder,
localization,
"resources.resjson",
)),
apiVersion: powerbiApi.version,
capabilitiesSchema: powerbiApi.schemas.capabilities,
pbivizSchema: powerbiApi.schemas.pbiviz,
stringResourcesSchema: powerbiApi.schemas.stringResources,
dependenciesSchema: powerbiApi.schemas.dependencies,
devMode: true,
generatePbiviz: false,
generateResources: true,
modules: true,
visualSourceLocation: "../../src/visual",
pluginLocation,
packageOutPath: path.join(__dirname, "dist"),
}),
new ExtraWatchWebpackPlugin({
files: [
pbivizPath,
capabilitiesPath,
],
dirs: [
"assets",
],
}),
new webpack.ProvidePlugin({
window: "realWindow",
define: "fakeDefine",
powerbi: "corePowerbiObject",
}),
],
};
现在可以使用了。结果,我认为,这是错误的组合,由于在 Power BI 中调试的困难而变得更加困难。大部分见解来自此 example and this template。我不确定,现在感恩节假期过后,这些问题中的哪一个解决了它,但这里有一些关键的变化:
导入 WebAssembly:在 visual.ts
中,我像这样引用了 emscripten 库:
import module from "emscriptenModule";
const moduleWasm = require("../node_modules/.../module.wasm");
import "regenerator-runtime/runtime";
...
module({
locateFile(filename: string) {
if (filename.endsWith(".wasm")) {
return moduleWasm;
}
return filename;
},
}).then((module) => {
this.module = module;
this.Init();
});
webpack.config.js 变化:
module: {
rules: [
{
test: /module\.js$/,
loader: "exports-loader",
},
// wasm files should not be processed but just be emitted and we want
// to have their public URL.
{
test: /module\.wasm$/,
type: "javascript/auto",
loader: "file-loader",
},
],
},
我还需要将 babel-polyfill 和 regenerator-runtime 添加到我的 package.json。
编辑
当转向生产时,我意识到输出依赖于 localhost:8080 的开发服务器。它现在正在处理以下附加更改:
webpack.config.js
{
test: /module\.wasm$/,
type: "javascript/auto",
loader: "base-64-loader", // <--
},
...
output: {
path: path.join(__dirname, ".tmp", "drop"),
publicPath: "assets", // <--
chunkFilename: "[name].js",
filename: "[name]",
},
我正在尝试在导入 emscripten 创建的 webassembly 库的 Typescript 项目上使用 webpack。这就是 Power BI 自定义视觉对象中的全部 运行,这让一切变得更加有趣。由于 Power BI 上下文、Typescript 和 WebAssembly,我认为这是额外的问题,但目前看来它可能只是一个 webpack 问题。
我认为是路径问题,但我是 webpack 的新手,有点迷茫。编译有效,但视觉对象为块 1 抛出一个 "ChunkLoadError"。(有两个块。)可能相关或不相关的事情:
- 我可以将浏览器定向到“https://localhost:8080/assets/1.js”(或...0.js)就好了。
- 我发现(我认为)因为 Power BI 将我的代码放在 iframe 中,最初 webpack 尝试的请求 url 是“https://app.powerbi.com/13.0.11428.218/assets/0.js". There was a baseUri property on the script tag it generated which caused this. Changing
output[publicPath]
to "https://localhost:8080/assets/” 允许浏览器下载块(我可以在浏览器的开发工具中看到它们成功进入),但 webpack 仍然报错。 - 尝试清除 node_modules 并重新安装。没有骰子。
在上述之后,错误如下所示:
VM1292:119 Uncaught (in promise) ChunkLoadError: Loading chunk 1 failed.
(missing: https://localhost:8080/assets/1.js)
at Function.requireEnsure [as e] (<anonymous>:119:26)
at new Visual (<anonymous>:23155:79)
at r.create [as creator] (<anonymous>:237:14)
at r.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:6200)
at https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:14488
at t.executeSafely (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:18033)
at t.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:20:14439)
at i.init (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:35819)
at i.executeMessage (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:42074)
at i.onMessageReceived (https://app.powerbi.com/13.0.11499.187/scripts/customVisualsHost.bundle.min.js:21:41772)
如果我暂停异常,我会在这里结束:
/******/ var error = new Error();
/******/ onScriptComplete = function (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;
/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
/******/ error.name = 'ChunkLoadError';
/******/ error.type = errorType;
/******/ error.request = realSrc;
/******/ ----> chunk[1](error);
/******/ }
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
"Paused on promise rejection" 但是,唉,我真的不知道我在看什么,或者为了什么。
这是我的 webpack.config.js:
const path = require("path");
const fs = require("fs");
const webpack = require("webpack");
console.log(require.resolve("powerbi-visuals-webpack-plugin"));
const PowerBICustomVisualsWebpackPlugin = require("powerbi-visuals-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const Visualizer = require("webpack-visualizer-plugin");
const ExtraWatchWebpackPlugin = require("extra-watch-webpack-plugin");
// api configuration
const powerbiApi = require("powerbi-visuals-api");
// visual configuration json path
const pbivizPath = "./pbiviz.json";
const pbivizFile = require(path.join(__dirname, pbivizPath));
// the visual capabilities content
const capabilitiesPath = "./capabilities.json";
const capabilitiesFile = require(path.join(__dirname, capabilitiesPath));
const pluginLocation = "./.tmp/precompile/visualPlugin.ts"; // path to visual plugin file, the file generates by the plugin
// string resources
// const resourcesFolder = path.join(".", "stringResources");
// const localizationFolders = fs.readdirSync(resourcesFolder);
const localizationFolders = [];
const resourcesFolder = ".";
// babel options to support IE11
const babelOptions = {
presets: [
[
require.resolve("@babel/preset-env"),
{
targets: {
ie: "11",
},
useBuiltIns: "entry",
modules: false,
corejs: "3",
},
],
],
sourceType: "unambiguous", // Tell babel that the project can contain different module types, not only es2015 modules.
cacheDirectory: path.join(".tmp", "babelCache"),
};
module.exports = {
entry: {
"visual.js": pluginLocation,
},
target: "web",
node: {
fs: "empty",
},
optimization: {
concatenateModules: false,
// minimize: true, // enable minimization for create *.pbiviz file less than 2 Mb, can be disabled for dev mode
},
devtool: "source-map",
mode: "development",
module: {
rules: [
{
parser: {
amd: false,
},
},
{
test: /(\.ts)x|\.ts$/,
include: /powerbi-visuals-|src|precompile\visualPlugin.ts/,
use: [
{
loader: require.resolve("babel-loader"),
options: babelOptions,
},
{
loader: require.resolve("ts-loader"),
options: {
transpileOnly: false,
experimentalWatchApi: false,
},
},
],
},
{
test: /(\.js)x|\.js$/,
use: [
{
loader: require.resolve("babel-loader"),
options: babelOptions,
},
],
},
{
test: /\.json$/,
loader: require.resolve("json-loader"),
type: "javascript/auto",
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: require.resolve("css-loader"),
},
{
loader: require.resolve("less-loader"),
options: {
paths: [path.resolve(__dirname, "..", "node_modules")],
},
},
],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: require.resolve("css-loader"),
},
],
},
{
test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp)$/i,
use: [
{
loader: "base64-inline-loader",
},
],
},
],
},
resolve: {
extensions: [".wasm", ".tsx", ".ts", ".jsx", ".js", ".css"],
},
output: {
path: path.join(__dirname, "/.tmp", "drop"),
publicPath: "https://localhost:8080/assets/",
chunkFilename: "[name].js",
filename: "[name]",
},
devServer: {
disableHostCheck: true,
contentBase: path.join(__dirname, ".tmp", "drop"), // path with assets for dev server, they are generated by webpack plugin
compress: true,
port: 8080, // dev server port
publicPath: "https://localhost:8080/assets/",
hot: false,
inline: false,
// cert files for dev server
https: {
pfx: fs.readFileSync(path.join(__dirname, "node_modules/powerbi-visuals-tools/certs/PowerBICustomVisualTest_public.pfx")), // for windows
passphrase: "##########",
},
headers: {
"access-control-allow-origin": "*",
"cache-control": "public, max-age=0",
},
},
externals: {
"powerbi-visuals-api": "null",
fakeDefine: "false",
corePowerbiObject: "Function('return this.powerbi')()",
realWindow: "Function('return this')()",
},
plugins: [
new MiniCssExtractPlugin({
filename: "visual.css",
chunkFilename: "[id].css",
}),
new Visualizer({
filename: "webpack.statistics.dev.html",
}),
// visual plugin regenerates with the visual source, but it does not require relaunching dev server
new webpack.WatchIgnorePlugin([
path.join(__dirname, pluginLocation),
"./.tmp/**/*.*",
]),
// custom visuals plugin instance with options
new PowerBICustomVisualsWebpackPlugin({
...pbivizFile,
capabilities: capabilitiesFile,
stringResources: localizationFolders.map((localization) => path.join(
resourcesFolder,
localization,
"resources.resjson",
)),
apiVersion: powerbiApi.version,
capabilitiesSchema: powerbiApi.schemas.capabilities,
pbivizSchema: powerbiApi.schemas.pbiviz,
stringResourcesSchema: powerbiApi.schemas.stringResources,
dependenciesSchema: powerbiApi.schemas.dependencies,
devMode: true,
generatePbiviz: false,
generateResources: true,
modules: true,
visualSourceLocation: "../../src/visual",
pluginLocation,
packageOutPath: path.join(__dirname, "dist"),
}),
new ExtraWatchWebpackPlugin({
files: [
pbivizPath,
capabilitiesPath,
],
dirs: [
"assets",
],
}),
new webpack.ProvidePlugin({
window: "realWindow",
define: "fakeDefine",
powerbi: "corePowerbiObject",
}),
],
};
现在可以使用了。结果,我认为,这是错误的组合,由于在 Power BI 中调试的困难而变得更加困难。大部分见解来自此 example and this template。我不确定,现在感恩节假期过后,这些问题中的哪一个解决了它,但这里有一些关键的变化:
导入 WebAssembly:在 visual.ts
中,我像这样引用了 emscripten 库:
import module from "emscriptenModule";
const moduleWasm = require("../node_modules/.../module.wasm");
import "regenerator-runtime/runtime";
...
module({
locateFile(filename: string) {
if (filename.endsWith(".wasm")) {
return moduleWasm;
}
return filename;
},
}).then((module) => {
this.module = module;
this.Init();
});
webpack.config.js 变化:
module: {
rules: [
{
test: /module\.js$/,
loader: "exports-loader",
},
// wasm files should not be processed but just be emitted and we want
// to have their public URL.
{
test: /module\.wasm$/,
type: "javascript/auto",
loader: "file-loader",
},
],
},
我还需要将 babel-polyfill 和 regenerator-runtime 添加到我的 package.json。
编辑 当转向生产时,我意识到输出依赖于 localhost:8080 的开发服务器。它现在正在处理以下附加更改:
webpack.config.js
{
test: /module\.wasm$/,
type: "javascript/auto",
loader: "base-64-loader", // <--
},
...
output: {
path: path.join(__dirname, ".tmp", "drop"),
publicPath: "assets", // <--
chunkFilename: "[name].js",
filename: "[name]",
},