我对Webpack还是很陌生,所以如果那是一个愚蠢的问题,请多多包涵。
我的目标是将我以前基于AMD的代码库转换为基于ES6模块的解决方案。我正在努力处理动态var video = document.getElementsByClassName("video");
var ratio = 16/9;
window.onresize = IframeVideoFix;
function IframeVideoFix(){
for(i = 0; i < video.length; i++){
video[i].style.height = video[i].offsetWidth/ratio;
}
}
setTimeout(IframeVideoFix, 500);
。因此,我的应用路由器基于模块工作,即,每条路由都映射到模块路径,然后import()
d。因为我知道,将包括哪些模块,所以我只需将那些动态导入的模块添加到我的r.js配置中,就可以在单个文件中构建所有内容,而所有需求调用仍然有效。
现在,我正在尝试对ES6模块和Webpack进行相同的操作。使用我的devmode,这没问题,因为我可以将require
替换为require()
。但是我无法使它与捆绑一起使用。 Webpack拆分了我的代码(无论如何仍然无法加载动态模块),或者-如果我将Array格式用于import()
配置,则捆绑中包含动态模块 ,但是加载仍然失败:entry
这是我的Webpack配置的样子:
Error: Cannot find module '/src/app/DynClass.js'
所以基本上我想告诉Webpack:“嘿,还有一个(或更多)要动态加载的模块,我希望它包含在捆绑软件中”
我该怎么做?
答案 0 :(得分:0)
是的,经过反复摆弄之后,隧道尽头似乎有光。不过,这不是100%的解决方案,并且肯定不是为了胆小的人,因为它非常丑陋且脆弱。但我仍然想与您分享我的方法:
我的路由器使用如下配置文件:
import StaticClass from "/src/app/StaticClass.js";
export default {
StaticClass: {
match: /^\//,
module: StaticClass
},
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
};
因此,您可以看到导出是一个对象,其中的键用作路由的ID,而对象则包含匹配项(基于正则表达式)和如果路由匹配应由路由器执行的模块。我可以为路由器提供构造函数(或对象),以立即可用(即包含在主块中)模块,或者如果模块值为字符串,则意味着路由器必须通过以下方式动态加载该模块:使用字符串中指定的路径。
据我所知,哪些模块可能会被加载(但是否以及何时加载),现在我可以在构建过程中解析该文件,并将路由配置转换为webpack可以理解的内容:>
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:\s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/\r?\n|\r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) => {
if (typeof routeConfig.module === "string") {
return acc + `import(/* webpackChunkName: "${routeName}" */"${routeConfig.module}");`;
}
return acc;
}, "") + "export default ''";
(是的,我知道这很丑陋,而且也有点脆,所以肯定可以做得更好)
本质上,我创建了一个新的虚拟模块,在该模块中,每个需要动态导入的路由条目都进行了翻译,因此:
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
成为:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
因此,路由ID只是成为了块的名称!
为此,我使用virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin({
moduleName: "./app/dummy.js",
contents: dummySource
})
],
dummySource
只是一个字符串,其中包含我刚刚生成的虚拟模块的源代码。现在,这个模块被拉进去了,webpack可以处理“虚拟导入”。但是,等等,我仍然需要导入虚拟模块,但是在我的开发模式中没有任何模块(我本机使用一切,因此没有加载程序)。
因此,在我的主要代码中,我执行以下操作:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) { import('./app/dummy.js'); }
在我处于开发模式时,“ dummy.js”只是一个空的存根模块。在构建时(使用webpack-loader-clean-pragma
加载程序)会删除特殊注释之间的部分,因此,当webpack“看到” dummy.js
的导入时,此代码将不会在构建本身中执行,{{ 1}}的值为isDev
。并且由于我们已经定义了具有相同路径的虚拟模块,因此在构建虚拟模块时就包含了该虚拟模块,就像我想要的一样,当然,所有依赖关系也都得到了解决。
对于开发来说,这很容易:
false
(请注意,这不是实际的路由器代码,仅足以帮助我进行PoC)
好吧,但是对于构建我还需要其他东西,因此我添加了一些特定于构建环境的代码:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) => {
if (typeof route.module === "function") {
new route.module;
} else {
const result = await import(route.module);
new result.default;
}
});
现在,仅删除了开发环境的加载代码,还有另一个内部使用webpack的加载代码。我还检查模块值是否为函数或字符串,如果为后者,则调用内部/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev) {
if (typeof route.module === "string") { await __webpack_require__.e(routeId); }
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
}
函数以加载正确的块:require.ensure
。还记得我在生成虚拟模块时为我的块命名吗?这就是为什么我现在仍然可以找到它们!
我遇到的另一件事是,当几个动态加载的模块具有相同的依赖关系时,webpack尝试生成更多名为await __webpack_require__.e(routeId);
之类的块,从而破坏了我的构建。为了解决这个问题,我需要确保所有这些共享模块都放入一个名为“ shared”的特定命名包中。
module1~module2.bundle.js
在生产模式下,我只是简单地手动加载此块,然后再根据它请求任何动态模块:
optimization: {
splitChunks: {
chunks: "all",
name: "shared"
}
}
同样,此代码仅在生产模式下运行!
最后,我必须防止webpack将模块(和大块)重命名为“ 1”,“ 2”等,而要保留我刚刚定义的名称:
if (!isDev) {
await __webpack_require__.e("shared");
}
是的,那里有!正如我所说的那样,这看起来并不漂亮,但似乎可以正常工作,至少在简化的测试设置中如此。我真的希望当我完成所有其他工作(例如ESLint,SCSS等)时,没有前方的障碍!