带有小型初始脚本和所有其他脚本的异步加载的 Webpack

Posted

技术标签:

【中文标题】带有小型初始脚本和所有其他脚本的异步加载的 Webpack【英文标题】:Webpack with small initial script and async loading of all other scripts 【发布时间】:2016-01-05 06:15:31 【问题描述】:

在开发由多个页面和不同页面类型组成的普通网站时,我已经开始使用 Webpack。我习惯了 RequireJs 脚本加载器,它在需要时按需加载所有依赖项。页面加载时只下载一小段 javascript

我想要实现的是:

一个小的初始 javascript 文件,用于异步加载依赖项 每种页面类型都可以有自己的 javascript,它们也可能有依赖关系。 通用模块、供应商脚本应捆绑在通用脚本中

我尝试了很多配置来实现这一点,但都没有成功。

entry: 
    main: 'main.js', //Used on all pages, e.g. mobile menu
    'standard-page': 'pages/standard-page.js',
    'start-page': 'pages/start-page.js',
    'vendor': ['jquery']
,
alias: 
    jquery: 'jquery/dist/jquery.js'
,
plugins: [
    new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js"),
    new webpack.optimize.CommonsChunkPlugin('common.js')
]

html 中,我想像这样加载 javascript:

<script src="/Static/js/dist/common.js"></script>
<script src="/Static/js/dist/main.js" async></script>

并且在特定的页面类型上(起始页)

&lt;script src="/Static/js/dist/start-page.js" async&gt;&lt;/script&gt;

common.js 应该是一个用于快速加载页面的小文件。 main.js 在里面加载 async 和 require('jquery')。

Webpack 的输出看起来很有希望,但我无法让供应商捆绑包异步加载。其他依赖项(我自己的模块和 domReady)被加载到自动生成的块中,但不是 jquery。

我可以找到很多几乎可以做到这一点但不是异步加载供应商的重要部分的示例。

来自 webpack 构建的输出:

                  Asset       Size  Chunks             Chunk Names
            main.js.map  570 bytes    0, 7  [emitted]  main
                main.js  399 bytes    0, 7  [emitted]  main
       standard-page.js  355 bytes    2, 7  [emitted]  standard-page
c6ff6378688eba5a294f.js  348 bytes    3, 7  [emitted]
          start-page.js  361 bytes    4, 7  [emitted]  start-page
8986b3741c0dddb9c762.js  387 bytes    5, 7  [emitted]
              vendor.js     257 kB    6, 7  [emitted]  vendor
              common.js    3.86 kB       7  [emitted]  common.js
2876de041eaa501e23a2.js     1.3 kB    1, 7  [emitted]  

【问题讨论】:

【参考方案1】:

解决这个问题的方法有两个:

    首先你需要了解how code-splitting works in webpack 其次,您需要使用 CommonsChunkPlugin 之类的东西来生成共享包。

代码拆分

在开始使用 webpack 之前,您需要忘记依赖配置。 Require.js 都是关于配置文件的。这种心态让我很难过渡到 webpack,它更接近于 node.js 中的 CommonJS 建模,它不依赖任何配置。

考虑到这一点,请考虑以下内容。如果您有一个应用程序并希望它异步加载 javascript 的其他部分,您需要使用以下范例之一。

要求.ensure

require.ensure 是您可以在应用程序中创建“分割点”的一种方式。同样,您可能认为您需要通过配置来执行此操作,但事实并非如此。在示例中,当我在文件中点击 require.ensure 时,webpack 将自动创建第二个包并按需加载它。在该分割点内执行的任何代码都将捆绑在一个单独的文件中。

require.ensure(['jquery'], function() 
    var $ = require('jquery');
    /* ... */
);

要求([])

您也可以使用 AMD 版本的 require() 实现相同的功能,它采用一系列依赖项。这也会创建相同的分割点:

require(['jquery'], function($) 
    /* ... */
);

共享包

在上面的示例中,您使用entry 创建一个包含jQuery 的vendor 包。您不需要手动指定这些依赖包。相反,使用 webpack 上面的分割点会自动生成它。

仅将entry 用于您希望在页面中使用的单独的&lt;script&gt; 标记

既然你已经完成了所有这些,你可以使用CommonsChunkPlugin 来进一步优化你的块,但是大部分魔法都是为你完成的,除了指定应该共享哪些依赖项之外,你不需要做任何其他事情。 webpack 将自动拉入共享块,无需额外的&lt;script&gt; 标签或entry 配置。

结论

您描述的场景(多个&lt;script&gt; 标签)实际上可能不是您想要的。使用 webpack,所有依赖项和包都可以从一个 &lt;script&gt; 标签开始自动管理。在经历了从 require.js 到 webpack 的多次重构之后,我发现这通常是管理依赖项的最简单和最好的方法。

一切顺利!

【讨论】:

谢谢!使用您的方法时,我的问题是 jquery 包含在异步加载的所有依赖包(a9114fd6e06bc77b48c9.js)中。我在多个条目文件中有 require.ensure(['jquery'] 。有没有办法只包含一个包含 jquery 的文件?然后我们可以使用缓存不多次加载 jquery。 是的,当然。首先。我很困惑你为什么要这么做require.ensure(['jquery'])。你根本不需要这样做。常见的块应该会自动为您分解这些大的东西,然后您可以使用&lt;script&gt; 标签将它们加载到您想要的任何地方。 听起来你想要这个示例描述的内容:github.com/webpack/webpack/tree/master/examples/… 你的依赖应用 js 文件将全部 require.ensure('[shared-bundle']) 由公共块吐出。 这也可能有帮助:github.com/webpack/docs/wiki/… 我 got it figured.. 需要设置块文件名选项【参考方案2】:

这是我想出的解决方案。

首先,将这两个函数导出到window.*——您需要在浏览器中使用它们。

export function requireAsync(module) 
    return new Promise((resolve, reject) => require(`bundle!./pages/$module`)(resolve));


export function runAsync(moduleName, data=) 
    return requireAsync(moduleName).then(module => 
        if(module.__esModule) 
            // if it's an es6 module, then the default function should be exported as module.default
            if(_.isFunction(module.default)) 
                return module.default(data);
            
         else if(_.isFunction(module)) 
            // if it's not an es6 module, then the module itself should be the function
            return module(data);
        
    )

然后,当您想在页面中包含一个脚本时,只需将其添加到您的 HTML 中:

<script>requireAsync('script_name.js')</script>

现在pages/ 目录中的所有内容都将被预编译成一个单独的块,可以在运行时异步加载,仅在需要时。

此外,使用上述函数,您现在可以方便地将服务器端数据传递到客户端脚本:

<script>runAsync('script_that_needs_data', my:'data',wow:'much excite')</script>

现在您可以访问它了:

// script_that_needs_data.js
export default function(my,wow) 
    console.log(my,wow);

【讨论】:

【参考方案3】:

我最近也走上了同样的路,我正在优化我的 Webpack 输出,因为我认为包太大,HTTP2 可以并行加载 js 文件,并且单独的文件缓存会更好,我得到了一些依赖项在捆绑包中重复,等等。虽然我得到了一个使用 Webpack 4 SplitChunksPlugin 配置的解决方案,但我目前主要使用 Webpack 的动态 import() 语法,因为正是这种语法会导致 Webpack 自动将动态导入的捆绑包捆绑在他们自己的文件中我可以通过“魔术评论”来命名:

import(/* webpackChunkName: "mymodule" */ "mymodule"); // I added an resolve.alias.mymodule entry in Webpack.config

【讨论】:

【参考方案4】:

前段时间我做了这么小的“概念证明”来检查 importlazy 在 IE11 中的工作方式。我不得不承认它有效:) 点击按钮后,加载了负责改变页面背景颜色的代码——full example

Js:

// polyfils for IE11
import 'core-js/modules/es.array.iterator';

const button = document.getElementById('background');

button.addEventListener('click', async (event) => 
  event.preventDefault();
  try 
    const background = await import(/* webpackChunkName: "background" */ `./$button.dataset.module.js`);
    background.default();
   catch (error) 
    console.log(error);
  
)

HTML:

<button id="background" class="button-primary" data-module="background">change the background</button>

【讨论】:

以上是关于带有小型初始脚本和所有其他脚本的异步加载的 Webpack的主要内容,如果未能解决你的问题,请参考以下文章

javascript脚本如何异步加载,有啥作用

JS异步那些事 四(HTML 5 Web Workers)

如果在异步加载 gmap 时未指定回调,则不会加载其他必要的谷歌地图脚本

SPDY CDN + 异步脚本加载后端

JavaScript异步编程 异步的脚本加载

加载异步脚本的艺术