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

时间:2020-01-21 10:13:19

标签: javascript webpack lazy-loading prefetch

我最近正在从事一些网站优化工作,并且我开始通过使用如下的import语句在webpack中使用代码拆分:

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

正确创建了 pageB-chunk.js 的现在,假设我要 prefetch 在pageA中,可以通过在pageA中添加以下语句来做到这一点:

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

哪个会导致

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

附加到HTML的头部后,浏览器将预取它,到目前为止效果很好。

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

这是在移动设备上非常耗时的操作,我想对其进行优化,我只想要 prefetch 部分,而不希望 evaluate&execute 部分,因为稍后在发生一些用户交互时,我将触发我自己进行分析和评估

enter image description here

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

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

Webpack是否有任何简单的方法来实现这一目标?

3 个答案:

答案 0 :(得分:2)

更新

您可以将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's babel plugin或与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. 您只能在async的帮助下延迟解析和评估, defer标记仅直到DOMContentLoaded 事件
  3. 如果您不插入脚本标签(只能将其link预加载),则可以延迟执行(评估)

现在还有其他一些不推荐 hackey的方式是您发送整个脚本和stringcomment(因为注释或字符串的评估时间几乎可以忽略)并且当您需要执行时可以同时使用Function() constructoreval


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

在现代浏览器中,您可以使用service worker来获取和缓存资源(JavaScript,图像,CSS等等),并且当对该资源的主线程请求时,您可以拦截该请求并以这种方式从缓存中返回资源。将脚本加载到缓存中时不会解析和评估脚本 了解有关服务人员here

的更多信息

示例

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

答案 1 :(得分:1)

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


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

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

  • 收集代码中的所有 import(args) 语句
  • 如果 import(args) 包含/ *预取: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 - Take control from webpack

再次,这是一种非常骇人听闻的方法,我不喜欢它,如果您希望Webpack团队实现这一点,请在这里投票:

Feature: prefetch dynamic import on demand

答案 2 :(得分:0)

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

const formik = useFormik({
        initialValues: {
          wflow_switch: false
        },
        onSubmit: values => {
          alert(JSON.stringify(values, null, 2));
        },
});