如何在Webpack捆绑包中包含手动import()

时间:2019-03-07 20:54:30

标签: javascript webpack es6-modules

我对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:“嘿,还有一个(或更多)要动态加载的模块,我希望它包含在捆绑软件中”

我该怎么做?

1 个答案:

答案 0 :(得分:0)

是的,经过反复摆弄之后,隧道尽头似乎有光。不过,这不是100%的解决方案,并且肯定不是为了胆小的人,因为它非常丑陋且脆弱。但我仍然想与您分享我的方法:

1)手动解析我的路由配置

我的路由器使用如下配置文件:

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只是成为了块的名称!

2)包括构建中的虚拟模块

为此,我使用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。并且由于我们已经定义了具有相同路径的虚拟模块,因此在构建虚拟模块时就包含了该虚拟模块,就像我想要的一样,当然,所有依赖关系也都得到了解决。

3)处理实际负载

对于开发来说,这很容易:

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。还记得我在生成虚拟模块时为我的块命名吗?这就是为什么我现在仍然可以找到它们!

4)还有很多事情要做

我遇到的另一件事是,当几个动态加载的模块具有相同的依赖关系时,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等)时,没有前方的障碍!