繁体   English   中英

在 webpack 上,如何在不评估脚本的情况下导入脚本?

[英]On webpack how can I import a script without evaluate it?

我最近在做一些网站优化工作,我开始通过使用 import 语句在 webpack 中使用代码拆分,如下所示:

import(/* webpackChunkName: 'pageB-chunk' */ './pageB')

正确创建pageB-chunk.js ,现在假设我想在 pageA 中预取这个块,我可以通过在 pageA 中添加以下语句来实现:

import(/* webpackChunkName: 'pageB-chunk' */ /* webpackPrefetch: true */ './pageB')

这将导致

<link rel="prefetch" href="pageB-chunk.js">

附加到 HTML 的头部,然后浏览器将预取它,到目前为止一切顺利。

问题是我在这里使用的import语句不仅预取 js 文件,而且评估 js 文件,意味着该 js 文件的代码被解析并编译为字节码,该 JS 的顶级代码被执行。

这在移动设备上是一个非常耗时的操作,我想优化它,我只想要预取部分,我不想要评估和执行部分,因为稍后当一些用户交互发生时,我会触发解析&自我评价

在此处输入图像描述

↑↑↑↑↑↑↑↑我只想触发前两步,图片来自https://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/ ↑↑↑↑↑↑↑ ↑↑

当然我可以通过自己添加预取链接来做到这一点,但这意味着我需要知道我应该在预取链接中放入哪个 URL,webpack 肯定知道这个 URL,我如何从 webpack 获取它?

webpack 是否有任何简单的方法来实现这一点?

更新

您可以将preload-webpack-pluginhtml-webpack-plugin一起使用,它会让您定义要在配置中预加载的内容,它会自动插入标签以预加载您的块

请注意,如果您现在使用的是 webpack v4,则必须使用preload-webpack-plugin@next安装此插件

例子

plugins: [
  new HtmlWebpackPlugin(),
  new PreloadWebpackPlugin({
    rel: 'preload',
    include: 'asyncChunks'
  })
]

对于生成两个具有动态生成名称的异步脚本的项目,例如chunk.31132ae6680e598f8879.jschunk.d15e7fdfc91b34bb78c4.js ,以下预加载将被注入文档head

<link rel="preload" as="script" href="chunk.31132ae6680e598f8879.js">
<link rel="preload" as="script" href="chunk.d15e7fdfc91b34bb78c4.js">

更新 2

如果您不想预加载所有异步块,但只预加载一次,您也可以这样做

您可以使用migcoder 的 babel 插件或使用preload-webpack-plugin ,如下所示

  1. 首先,您必须借助webpack magic comment示例来命名该异步块

    import(/* webpackChunkName: 'myAsyncPreloadChunk' */ './path/to/file')
  2. 然后在插件配置中使用该名称

    plugins: [ new HtmlWebpackPlugin(), new PreloadWebpackPlugin({ rel: 'preload', include: ['myAsyncPreloadChunk'] }) ]

首先让我们看看当我们指定script标签或link标签加载脚本时浏览器的行为

  1. 每当浏览器遇到script标签时,它都会加载它并解析它并立即执行
  2. 您只能在asyncdefer标记的帮助下延迟解析和评估,直到DOMContentLoaded事件
  3. 如果您不插入脚本标签(仅使用link预加载),您可以延迟执行(评估)

现在有一些其他不推荐的 hackkey 方法是你发送你的整个脚本和stringcomment (因为评论或字符串的评估时间几乎可以忽略不计)并且当你需要执行时你可以使用Function() constructoreval两者都不是推荐的


另一种方法服务工作者:(这将在页面重新加载后保留您的缓存事件,或者在加载缓存后用户离线)

在现代浏览器中,您可以使用service worker来获取和缓存资源(JavaScript、图像、css 任何东西),当主线程请求该资源时,您可以拦截该请求并从缓存中返回资源,这样您就不会解析和评估将脚本加载到缓存中时在此处阅读有关服务工作者的更多信息

例子

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    // caches.match() always resolves
    // but in case of success response will have value
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        // response may be used only once
        // we need to save clone to put one copy in cache
        // and serve second one
        let responseClone = response.clone();

        caches.open('v1').then(function (cache) {
          cache.put(event.request, responseClone);
        });
        return response;
      }).catch(function () {
        // any fallback code here
      });
    }
  }));
});

正如你所看到的,这不是一个依赖于 webpack的东西,这超出了 webpack 的范围,但是在 webpack 的帮助下,你可以拆分你的包,这将有助于更好地利用 service worker

更新:我将所有东西都包含在一个 npm 包中,检查一下! https://www.npmjs.com/package/webpack-prefetcher


经过几天的研究,我最终编写了一个自定义的 babel 插件......

简而言之,插件的工作方式如下:

  • 收集代码中的所有import(args)语句
  • 如果import(args)包含 /* prefetch: true */ 注释
  • import()语句中找到chunkId
  • 将其替换为Prefetcher.fetch(chunkId)

Prefetcher 是一个包含 webpack 输出清单的辅助类,可以帮助我们插入预取链接:

 export class Prefetcher { static manifest = { "pageA.js": "/pageA.hash.js", "app.js": "/app.hash.js", "index.html": "/index.html" } static function fetch(chunkId) { const link = document.createElement('link') link.rel = "prefetch" link.as = "script" link.href = Prefetcher.manifest[chunkId + '.js'] document.head.appendChild(link) } }

用法示例:

 const pageAImporter = { prefetch: () => import(/* prefetch: true */ './pageA.js') load: () => import(/* webpackChunkName: 'pageA' */ './pageA.js') } a.onmousehover = () => pageAImporter.prefetch() a.onclick = () => pageAImporter.load().then(...)

这个插件的细节可以在这里找到:

Prefetch - 从 webpack 获取控制权

再一次,这是一个非常 hacky 的方式,我不喜欢它,如果你想让 webpack 团队实现这个,请在这里投票:

功能:按需预取动态导入

假设我了解您要实现的目标,您希望在给定事件(例如单击按钮)后解析和执行模块。 您可以简单地将 import 语句放在该事件中:

element.addEventListener('click', async () => {
  const module = await import("...");
});

暂无
暂无

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

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