当前位置: 首页 > news >正文

你应该如何引入JavaScript

vite build

当我们用vite build时,默认会将依赖文件以modulepreload的方式预加载。

<script type="module" crossorigin src="/assets/main-W15RtDcg.js"></script>
<link rel="preload" as="script" crossorigin href="/assets/modulepreload-polyfill-DaKOjhqt.js">
<link rel="preload" as="script" crossorigin href="/assets/vue-B55glXv2.js">
<link rel="preload" as="script" crossorigin href="/assets/pinia-Csf_hNVa.js">
<link rel="stylesheet" crossorigin href="/assets/main-DIVsyA-7.css">

其中甚至会增加一个modulepreloadpolyfill

(function polyfill() {// 1. 检测是否支持<link rel="modulepreload" >// 支持则return,即利用浏览器的默认行为,不再做额外处理const relList = document.createElement("link").relList;if (relList && relList.supports && relList.supports("modulepreload")) {return;}// 2. 不支持则利用js模拟,浓缩成processPreload函数// 2.1 处理已有的linksfor (const link of document.querySelectorAll('link[rel="modulepreload"]')) {processPreload(link);}// 2.2 监听可能新加的linksnew MutationObserver((mutations) => {for (const mutation of mutations) {if (mutation.type !== "childList") {continue;}for (const node of mutation.addedNodes) {if (node.tagName === "LINK" && node.rel === "modulepreload")processPreload(node);}}}).observe(document, { childList: true, subtree: true });function getFetchOpts(link) {const fetchOpts = {};if (link.integrity) fetchOpts.integrity = link.integrity;if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;if (link.crossOrigin === "use-credentials")fetchOpts.credentials = "include";else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";else fetchOpts.credentials = "same-origin";return fetchOpts;}function processPreload(link) {if (link.ep)return;link.ep = true;const fetchOpts = getFetchOpts(link);fetch(link.href, fetchOpts);}
})();

HTML中如何引入JavaScript

inline

内联,解析到即执行。

<script>const a = 1;
</script>

external

独立文件,解析到的时候,资源需要先下载再执行。阻塞HTML继续解析。

<script src="main.js"></script>

在下载JavaScript资源时没必要阻塞HTML解析。

<script async />

下载时继续HTML解析,下载完再立刻执行。

<script async src="main.js"></script>

适用于,JavaScript内容与DOM结构无关的资源。

<script defer />

下载时继续解析HTML,并等到document解析完之后再按书写顺序依次执行。

<script defer src="main.js"></script>

适用于,依赖了DOM结构的资源,一般如入口文件。

至此,解决了JavaScript的下载阻塞HTML解析的问题。

同时,JavaScript也发展到了模块时代。

JavaScript programs started off pretty small — most of its usage in the early days was to do isolated scripting tasks, providing a bit of interactivity to your web pages where needed, so large scripts were generally not needed. Fast forward a few years and we now have complete applications being run in browsers with a lot of JavaScript, as well as JavaScript being used in other contexts (Node.js, for example).

JavaScript modules - JavaScript | MDN

[图片来自: https://html.spec.whatwg.org/images/asyncdefer.svg]

<script type="module" />

默认是defer的。

<script type="module" src="main.js"></script>

模块带来了静态依赖关系,在构建侧促进了tree-shaking的应用发展。在浏览器侧,也被vite利用来做一个可以快速启动的dev-server

In addition to enabling the use of ES modules, Rollup also statically analyzes the code you are importing, and will exclude anything that isn't actually used. This allows you to build on top of existing tools and modules without adding extra dependencies or bloating the size of your project.

Since this approach can utilise explicit import and export statements, it is more effective than simply running an automated minifier to detect unused variables in the compiled output code.

Introduction | Rollup

This is essentially letting the browser take over part of the job of a bundler: Vite only needs to transform and serve source code on demand, as the browser requests it.

Why Vite | Vite

利用浏览器作依赖分析的弊端在于深层资源依赖的下载瀑布。

Even though native ESM is now widely supported, shipping unbundled ESM in production is still inefficient (even with HTTP/2) due to the additional network round trips caused by nested imports.

Why Vite | Vite

Even though the server has no problem handling them, the large amount of requests create a network congestion on the browser side, causing the page to load noticeably slower.

Dependency Pre-Bundling | Vite

nested_imports

这也是为什么还需要打包的理由,打包有一个重要的作用就是,把nested抹平,使浏览器尽可能少地发起网络动作,减少RoundTrip

All modern browsers support module features natively without needing transpilation. It can only be a good thing — browsers can optimize loading of modules, making it more efficient than having to use a library and do all of that extra client-side processing and extra round trips. It does not obsolete bundlers like webpack, though — bundlers still do a good job at partitioning code into reasonably sized chunks, and are able to do other optimizations like minification, dead code elimination, and tree-shaking.

JavaScript modules - JavaScript | MDN

不用等HTML解析到<script>标签才下载,而是更早地下载。

<link rel="preload" />

提前下载。

The preload value of the <link> element's rel attribute lets you declare fetch requests in the HTML's <head>, specifying resources that your page will need very soon, which you want to start loading early in the page lifecycle, before browsers' main rendering machinery kicks in. This ensures they are available earlier and are less likely to block the page's render, improving performance. Even though the name contains the term load, it doesn't load and execute the script but only schedules it to be downloaded and cached with a higher priority.

rel=preload - HTML | MDN

<head><meta charset="utf-8" /><title>JS and CSS preload example</title><link rel="preload" href="style.css" as="style" /><link rel="preload" href="main.js" as="script" /><link rel="stylesheet" href="style.css" />
</head><body><h1>bouncing balls</h1><canvas></canvas><script src="main.js" defer></script>
</body>

去掉也毫无影响,只是作为性能优化手段指示性地告诉浏览器,我马上要用到这个资源,你去下载吧!这对于深层资源效果更加明显,原本需要下载A,解析A,然后发现A中依赖B,再去下载B,解析B,甚至CDEF,这将是巨大的嵌套地狱。通过preload指示性地声明所依赖的子资源,使浏览器提前感知并下载,在后续解析到时直接从cache中获取资源。

script作为特殊的资源,代表普遍意义的脚本,但是

对于ESMscript,也即module,更特殊地,我们可以提前解析优化。

preloadprefetch区别
  • prefetch: followup/future/next navigation
  • preload: current navigation

The prefetch keyword for the rel attribute of the <link> element provides a hint to browsers that the user is likely to need the target resource for future navigations, and therefore the browser can likely improve the user experience by preemptively fetching and caching the resource. <link rel="prefetch"> is used for same-site navigation resources, or for subresources used by same-site pages.

rel=prefetch - HTML | MDN

脚本(classic script)和模块(module)的区别

  • You need to pay attention to local testing — if you try to load the HTML file locally (i.e., with a file:// URL), you'll run into CORS errors due to JavaScript module security requirements. You need to do your testing through a server.
  • Also, note that you might get different behavior from sections of script defined inside modules as opposed to in classic scripts. This is because modules use strict mode automatically.
  • There is no need to use the defer attribute (see <script>attributes) when loading a module script; modules are deferred automatically.
  • Modules are only executed once, even if they have been referenced in multiple <script> tags.
  • Last but not least, let's make this clear — module features are imported into the scope of a single script — they aren't available in the global scope. Therefore, you will only be able to access imported features in the script they are imported into, and you won't be able to access them from the JavaScript console, for example. You'll still get syntax errors shown in the DevTools, but you'll not be able to use some of the debugging techniques you might have expected to use.

JavaScript modules - JavaScript | MDN

<link rel="modulepreload" />

The modulepreload keyword, for the rel attribute of the <link> element, provides a declarative way to preemptively fetch a module script, parse and compile it, and store it in the document's module map for later execution.

Preloading allows modules and their dependencies to be downloaded early, and can also significantly reduce the overall download and processing time.

Links with rel="modulepreload" are similar to those with rel="preload". The main difference is that preload just downloads the file and stores it in the cache, while modulepreload gets the module, parses and compiles it, and puts the results into the module map so that it is ready to execute.

use <link> elements with rel="modulepreload" for the main file and each of the dependency modules. This is much faster because the three modules all start downloading asynchronously and in parallel before they are needed. By the time main.js has been parsed and its dependencies are known, they have already been fetched and downloaded.

rel="modulepreload" - HTML | MDN

index.html
main.js
modules/canvas.jssquare.js
<link rel="modulepreload" href="main.js" />
<link rel="modulepreload" href="modules/canvas.js" />
<link rel="modulepreload" href="modules/square.js" /><script type="module" src="main.js"></script>

modulepreload: parse and compile 都在线程池完成

preload: 主线程依然需要compile code

行为差异的关键就在于,preload 缺乏 module 信息,未提供给浏览器,所以不能做更极致的优化。

小结

总之,作为开发者,越精确地将我们知道的信息传递给浏览器,就能获得越多的优化。指示信息的缺失并不会影响程序的进行,但是提供指示信息却能将效率提高不少。

这种思想用在很多地方:

  • bundler提供/*#__PURE__*/纯函数标记,可以安全tree-shaking
  • v-for提供key,可以精确地做更新
  • addEventListener()提供passive: true,避免浏览器默认行为渲染阻塞。
  • 给元素CSS提供will-change属性,提示浏览器哪些属性即将发生变化,让浏览器提前优化渲染流程(如分配内存、创建复合层),从而减少样式变化时的卡顿,提升动画和交互性能。

If an event has a default action — for example, a wheel event that scrolls the container by default — the browser is in general unable to start the default action until the event listener has finished, because it doesn't know in advance whether the event listener might cancel the default action by calling Event.preventDefault(). If the event listener takes too long to execute, this can cause a noticeable delay, also known as jank, before the default action can be executed.

By setting the passive option to true, an event listener declares that it will not cancel the default action, so the browser can start the default action immediately, without waiting for the listener to finish. If the listener does then call Event.preventDefault(), this will have no effect.

EventTarget: addEventListener() method - Web APIs | MDN

More

当我们将眼光从浏览器再往前看,来到服务端将HTML传给浏览器之前。

服务端了解HTML要比浏览器早得多。

那么作为网站的维护者,我们何必等到用户的浏览器拿到HTML,分析HTML,请求资源的时候再下发资源呢?

HTTP2 Push

在收到HTML的请求时,将所需要的资源主动推送给浏览器。但也有问题:

  • 强制推送,也许浏览器端已有缓存,则不需要更新

HTTP3 103 Early Hints

做一次协商,服务端发送期望的资源链接,由浏览器决定具体是否请求。

The HTTP 103 Early Hints informational response may be sent by a server while it is still preparing a response, with hints about the sites and resources that the server expects the final response will link to. This allows a browser to preconnect to sites or start preloading resources even before the server has prepared and sent a final response. Preloaded resources indicated by early hints are fetched by the client as soon as the hints are received.

103 Early Hints - HTTP | MDN

References

  • https://developer.mozilla.org/
  • https://html.spec.whatwg.org/
  • https://vite.dev
  • https://rollupjs.org

相关文章:

  • 再现重大BUG,微软紧急撤回Win 11六月更新
  • 力扣HOT100之技巧:31. 下一个排列
  • 学习笔记整理之状态图与状态图搜索
  • AI模型的泛化性的第一性原理是什么?
  • 解释器模式(Interpreter Pattern)
  • Spark on yarn的作业提交流程
  • AppInventor2原生进度条组件LinearProgress用法及注意点
  • 试过沃尔玛的无人机送货吗?今年覆盖范围将翻番
  • 傲火集团传媒基地武汉启幕 构建数字娱乐产业生态闭环
  • yolov5环境配置
  • 拉深工艺——有凸缘圆筒形件的拉深(实例分析)
  • slam--运动方程和观测方程
  • 【驱动设计的硬件基础】处理器的分类
  • 解决蓝牙MAC 地址倒序问题
  • 如何快速删除谷歌浏览器在mac启动台生成的网页图标
  • 从零开始学Python(3)——函数
  • python-76-基于uv的python虚拟环境和包管理工具
  • 基于大模型预测单纯性孔源性视网膜脱离的技术方案大纲
  • Makefile 学习笔记
  • AI大模型从0到1记录学习 大模型技术之机器学习 day27-day60
  • 全屋定制十大品牌/企业关键词优化专业公司
  • 做网站的职业叫什么/湖南关键词优化品牌价格
  • 乐山沙湾区住房建设局网站/网上营销怎么做
  • 武汉网络兼职网站建设/中央刚刚宣布大消息
  • 英文做影评的网站/大连企业网站建站模板
  • web个人网站开发/石家庄seo全网营销