繁体   English   中英

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

[英]How to include manual import() in Webpack Bundle

我是Webpack的新手,如果那是一个愚蠢的问题,请多多包涵。

我的目标是将基于AMD的旧代码库转换为基于ES6模块的解决方案。 我正在努力处理动态import() 因此,我的应用路由器基于模块工作,即每个路由都映射到模块路径,然后require d。 因为我知道将包含哪些模块,所以我只需将那些动态导入的模块添加到我的r.js配置中,就可以在单个文件中构建所有内容,并且所有require调用仍然有效。

现在,我正在尝试对ES6模块和Webpack进行同样的操作。 在我的devmode中,这没问题,因为我可以将import()替换为require() import() 但是我无法使它与捆绑一起使用。 无论的WebPack分裂我的代码(现在仍然未能反正加载动态模块),或者-如果我使用Array格式的entry配置,动态模块包含在捆绑,但负载仍然失败: Error: Cannot find module '/src/app/DynClass.js'

这是我的Webpack配置的样子:

const webpack = require('webpack');
const path = require('path');

module.exports = {
    mode: "development",
    entry: ['./main.js', './app/DynClass.js'],
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, "../client/")
    },
    resolve: {
        alias: {
            "/src": path.resolve(__dirname, '')
        }
    },
    module: {
        rules: [
            {
                test: /\.tpl$/i,
                use: 'raw-loader',
            },
        ]
    }
};

因此,基本上我想告诉Webpack:“嘿,还有一个(或更多)要动态加载的模块,我希望它包含在捆绑软件中”

我怎样才能做到这一点?

是的,经过反复摆弄之后,隧道尽头似乎有光。 不过,这不是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的导入时,此代码将不会在构建本身中执行,因为isDev对其求值false 并且由于我们已经定义了具有相同路径的虚拟模块,因此在构建虚拟模块时就包括了该模块,当然,所有依赖关系也都得到了解决。

3)处理实际装载

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

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;
    }
});

(请注意,这不是实际的路由器代码,仅足以帮助我进行PoC)

好吧,但是对于构建我还需要其他东西,因此我添加了一些特定于构建环境的代码:

/** @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;
}  

现在,仅删除了开发环境的加载代码,还有另一个在内部使用webpack的加载代码。 我还检查模块值是否为函数或字符串,如果为后者,则调用内部require.ensure函数以加载正确的块: await __webpack_require__.e(routeId); 还记得我在生成虚拟模块时为我的块命名吗? 这就是为什么我现在仍然可以找到它们!

4)还有更多工作要做

我遇到的另一件事是,当几个动态加载的模块具有相同的依存关系时,webpack尝试生成更多名为诸如module1~module2.bundle.js类的块,从而破坏了我的构建。 为了解决这个问题,我需要确保所有这些共享模块都放入一个名为“ shared”的特定命名包中:

optimization: {
    splitChunks: {
        chunks: "all",
        name: "shared"
    }
} 

在生产模式下,我可以简单地手动加载此块,然后再根据它请求任何动态模块:

if (!isDev) {
    await __webpack_require__.e("shared");
}

同样,此代码仅在生产模式下运行!

最后,我必须防止webpack将模块(和大块)重命名为“ 1”,“ 2”等,而是保留我刚刚定义的名称:

optimization: {
    namedChunks: true,
    namedModules: true
}

是的,那里有! 正如我所说的那样,这看起来并不漂亮,但似乎可以正常工作,至少在简化的测试设置中如此。 我真的希望当我完成所有其他工作时(例如ESLint,SCSS等),我面前没有任何障碍!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM