打包非 JavaScript 静态资源详情

目录
  • 1、打包工具中的自定义导入
  • 2、浏览器和打包工具中通用的导入语法
  • 3、模棱两可的相对URL
  • 4、工具链中的支持
    • 4.1 打包工具
  • 5、 WebAssembly
    • 5.1  通过Emscripten编译的C/C++
    • 5.2 通过 wasm-pack / wasm-bindgen 编译的 Rust
  • 6、未来的导入方式
    • 6.1  import.meta.resolve
    • 6.2 导入断言
  • 7、小结
  • 8、参考资料

本文翻译自 https://web.dev/bundling-non-js-resources/,原文未做修改

假设你正在开发一个网络应用程序。在这种情况下,你很可能不仅要处理 JavaScript 模块,还要处理各种其他资源--Web Workers(它也是 JavaScript ,但它拥有一套独立的构建依赖图)、图片、CSS、字体、WebAssembly 模块等等。

一种可行的加载静态资源的办法是在 HTML 中直接引用它们,但通常它们在逻辑上是与其他可重用的组件耦合的。例如,自定义下拉菜单的 CSS 与它的 JavaScript 部分相联系,图标图像与工具栏组件相关,而 WebAssembly 模块与它的 JavaScript 胶水相依赖。在这些情况下,有种更加方便快捷的办法是直接从它们的 JavaScript 模块中引用资源,并在加载相应的组件时动态地加载它们。

然而,大多数大型项目的构建系统都会对内容进行额外的优化和重组--例如打包和最小化(minimize)。构建系统不能执行代码并预测执行的结果是什么,也没理由去遍历判断 JavaScript 中每一个可能的字符串是否是一个资源 URL。那么,如何才能让它们 "看到 "那些由 JavaScript 组件加载的动态资源,并将它们包含在构建产物中呢?

1、打包工具中的自定义导入

一种常见的方法是利用已有的静态导入语法。有些打包工具可能会通过文件扩展名来自动检测格式,而有些其他打包工具则允许插件使用自定义的 URL Scheme比如下面的例子:

// 普通 JavaScript 导入
import { loadImg } from './utils.js';

// 特殊 "URL 导入" 的静态资源
import imageUrl from 'asset-url:./image.png';
import wasmUrl from 'asset-url:./module.wasm';
import workerUrl from 'js-url:./worker.js';

loadImg(imageUrl);
WebAssembly.instantiateStreaming(fetch(wasmUrl));
new Worker(workerUrl);

当一个打包工具插件发现一个导入项带有它所识别的扩展名或 URL Scheme(上面的例子中的 asset-url: 和 js-url: )时,它会将引用的资源添加到构建图中,将其复制到最终目的地,执行适用于资源类型的优化,并返回最终的 URL,以便在运行时使用。

这种方法的好处是:重用 JavaScript 导入语法,保证所有的 URL 都是静态的相对路径,这使得构建系统很容易定位这种依赖关系。

然而,它有一个明显的缺点:这种代码不能直接在浏览器中工作,因为浏览器不知道如何处理那些自定义的导入方案或扩展名。当然,如果你可以控制所有的代码,并且本来就要依靠打包工具进行开发,这听起来还不错。然而为了减少麻烦,直接在浏览器中使用 JavaScript 模块的情况越来越普遍(至少在开发过程中是这样)。一个小 demo 可能根本就不需要打包工具,即使在生产中也不需要。

2、浏览器和打包工具中通用的导入语法

如果你正在开发一个可重用的组件,你会希望它在任何环境下都能发挥作用,无论它是直接在浏览器中使用还是作为一个更大的应用程序的一部分预先构建。大多数现代的打包工具都接受下面这个JavaScript 模块导入语法:

new URL('./relative-path', import.meta.url)

它看着像是一种特殊的语法,然而它确实是一种有效的 JavaScript 表达式,可以直接在浏览器中使用,也可以被打包工具静态地检测出来并加以处理。

使用这个语法,前面的例子可以改写为:

// regular JavaScript import
import { loadImg } from './utils.js';
loadImg(new URL('./image.png', import.meta.url));
WebAssembly.instantiateStreaming(
  fetch(new URL('./module.wasm', import.meta.url)),
  { /* … */ }
);
new Worker(new URL('./worker.js', import.meta.url));

让我们分析一下它是什么原理: new URL(...) 构造函数会基于第二个参数里的绝对URL,解析出第一个参数中相对 URL 所对应的 URL。在我们的例子中,第二个参数是 import.meta.url [1] ,它是当前 JavaScript 模块的 URL ,所以第一个参数可以是相对于它的任何路径。

它的优点和劣势都类似于 动态导入。虽然可以使用 import(...) 导入内容,如 import(someUrl) ,但打包工具会特殊处理带有静态 URL import('./some-static-url.js') 的导入方式:把它作为一种在编译时预处理已知依赖关系的导入方式,把 代码分块 并动态加载。

同样,你可以使用 new URL(...) ,如 new URL(relativeUrl, customAbsoluteBase) ,然而 new URL('...', import.meta.url) 语法可以明确地告诉打包工具预处理依赖,并将其与主 JavaScript 资源打包在一起。

3、模棱两可的相对URL

你可能会想,为什么打包工具不能检测到其他常见的语法--例如,没有 new URL 包装的 fetch('./module.wasm') ?

原因是,与 import 关键字不同,任何动态请求都是相对于文档本身的,而不是相对于当前的JavaScript文件进行解析。比方说,我们有以下结构:

index.html:

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

src/

main.js

module.wasm

如果你想从 main.js 中加载 module.wasm ,你的第一反应可能是使用 fetch('./module.wasm') 这样的相对路径引用。

然而,fetch不知道它所执行的 JavaScript 文件的 URL,相反,它是相对于文档来解析 URL 的。因此, fetch('./module.wasm') 最终会试图加载 http://example.com/module.wasm ,而不是预期的 http://example.com/src/module.wasm ,从而造成失败(运气更不好的情况下,还可能默默地加载一个与你预期不同的资源)。

通过将相对的URL包装成 new URL('...', import.meta.url) ,你可以避免这个问题,并保证任何提供的URL在传递给任何loader之前都是相对于 当前 JavaScript 模块的 URL(import.meta.url) 解析的。

只要用 fetch(new URL('./module.wasm', import.meta.url)) 代替 fetch('./module.wasm') ,就可以成功地加载预期的 WebAssembly 模块,同时给打包工具一个在构建时找到这些相对路径的可靠方法。

4、工具链中的支持

4.1 打包工具

下面这些打包工具已经支持 new URL 语法:

  • Webpack v5
  • Rollup (通过插件支持: @web/rollup-plugin-import-meta-assets  支持通用资源,而 @surma/rollup-plugin-off-main-thread 支持 Workers.)
  • Parcel v2 (beta) (译者注:在本译文发布时,Parcel V2已经正式发布:https://parceljs.org/blog/v2
  • Vite

5、 WebAssembly

当使用 WebAssembly 时,你通常不会手动加载 Wasm 模块,而是导入由工具链发出的 JavaScript 胶水代码。下面的工具链可以替你生成 new URL(...) 语法:

5.1  通过Emscripten编译的C/C++

当使用 Emscripten 工具链时,我们可以通过以下选项要求它输出 ES6 模块胶水代码,而非普通 JS 代码:

$ emcc input.cpp -o output.mjs
## 如果你不想用mjs扩展名:
$ emcc input.cpp -o output.js -s EXPORT_ES6

当使用这个选项时,输出的胶水代码将使用new URL(..., import.meta.url) 语法,这样打包工具可以自动找到相关的 Wasm 文件。

通过添加 -pthread 参数,这个语法也可以支持 WebAssembly 线程的编译

$ emcc input.cpp -o output.mjs -pthread
## 如果你不想用mjs扩展名:
$ emcc input.cpp -o output.js -s EXPORT_ES6 -pthread

在这种情况下,生成的Web Worker将以同样的方式被引用,并且也能被打包工具和浏览器正确加载。

5.2 通过 wasm-pack / wasm-bindgen 编译的 Rust

wasm-pack  --WebAssembly 的主要 Rust 工具链,也有几种输出模式。

默认情况下,它将输出一个依赖于 WebAssembly ESM 集成提议 的 JavaScript 模块。在写这篇文章的时候,这个提议仍然是实验性的,只有在使用 Webpack 打包时,输出才会有效。

或者,我们可以通过 -target web 参数要求 wasm-pack 通过输出一个与浏览器兼容的 ES6 模块:

$ wasm-pack build --target web

输出将使用前面所说的 new URL(..., import.meta.url) 语法,而且 Wasm 文件也会被打包工具自动发现。

如果你想通过 Rust 使用 WebAssembly 线程,这就有点复杂了。请查看指南的 相应部分 [13] 以了解更多。

简而言之,你不能使用任意的线程 API,但如果你使用 Rayon [14] ,你可以试试 wasm-bingen-rayon [15] 适配器,这样它就可以生成 Web 上可以运行的 Worker 。 wasm-bindgen-rayon 使用的 JavaScript 胶水 也包括 [16] new URL (...)语法,因此 Workers 也能被打包工具发现和引入。

6、未来的导入方式

6.1  import.meta.resolve

有一个潜在的未来改进是专门的 import.meta.resolve(...) 语法。它将允许以一种更直接的方式解析相对于当前模块的内容,而不需要额外的参数。

// 现在的语法
new URL('...', import.meta.url)

// 未来的语法
await import.meta.resolve('...')

它还能与导入依赖图(import maps)还有自定义解析器更好地整合,因为它和 import 语法通过同一个模块解析系统处理。这对打包工具来说也是一个更可靠的信号,因为它是一个静态语法,不依赖于像 URL 这样的运行时 API 。

import.meta.resolve 已经作为一个 实验性功能 在 Node.js 中实现了,但是关于它在 Web 上应该如何工作 还有一些问题没有定论。

6.2 导入断言

导入断言(import assertions)是一项新功能,允许导入 ECMAScript 模块以外的类型,不过现在只支持JSON 类型。

foo.json

{ "answer": 42 }

main.mjs

import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42

(译者注:关于这个不太符合直觉的语法选择也有点意思 https://github.com/tc39/proposal-import-assertions/issues/12)

它们也可能被打包工具使用,并取代目前由new URL语法所支持的场景,但导入断言中的类型需要一个一个被支持,目前被支持的只有 JSONCSS 模块即将被支持,但其他类型的资源导入仍然需要一个更通用的解决方案。

要想了解更多关于这个功能的信息,请查看 v8.dev上的功能解释 [19] 。

7、小结

正如你所看到的,有各种方法可以在网络上包含非 JavaScript 资源,但它们有各自的优缺点,而且都不能同时在所有工具链中工作。一些未来的提议可能会让我们用专门的语法来导入这些资源,但我们还没有走到这一步。

在那一天到来之前, new URL(..., import.meta.url) 语法是最有希望的解决方案,并且今天已经可以在浏览器、各种捆绑器和 WebAssembly 工具链中工作。

到此这篇关于打包非 JavaScript 静态资源详情的文章就介绍到这了,更多相关打包非 JavaScript 静态资源内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

8、参考资料

[1];import.meta.url: https://v8.dev/features/modules#import-meta

[2];动态导入: https://v8.dev/features/dynamic-import

[3]:代码分块: https://web.dev/reduce-javascript-payloads-with-code-splitting/

[4]:Webpack v5: https://webpack.js.org/guides/asset-modules/#url-assets

[5]:Rollup: https://rollupjs.org/

[6]:@web/rollup-plugin-import-meta-assets: https://modern-web.dev/docs/building/rollup-plugin-import-meta-assets/

[7]:@surma/rollup-plugin-off-main-thread: https://github.com/surma/rollup-plugin-off-main-thread

[8]:Parcel v2 (beta): https://v2.parceljs.org/languages/javascript/#url-dependencies

[9]:Vite: https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url

[10]:WebAssembly: https://web.dev/webassembly-threads/#c

[11]:wasm-pack: https://github.com/rustwasm/wasm-pack

[12]:WebAssembly ESM 集成提议: https://github.com/WebAssembly/esm-integration

[13]:相应部分: https://web.dev/webassembly-threads/#rust

[14]:Rayon: https://github.com/rayon-rs/rayon

[15]:wasm-bingen-rayon: https://github.com/GoogleChromeLabs/wasm-bindgen-rayon

[16]:也包括: https://github.com/GoogleChromeLabs/wasm-bindgen-rayon/blob/4cd0666d2089886d6e8731de2371e7210f848c5d/demo/index.js#L26

[17]:实验性功能: https://nodejs.org/api/esm.html#esm_import_meta_resolve_specifier_parent

[18]:还有一些问题没有定论: https://github.com/WICG/import-maps/issues/79

[19]:v8.dev上的功能解释: https://v8.dev/features/import-assertions

(0)

相关推荐

  • Nuxt.js 静态资源和打包的操作

    直接引入图片 我们在网上任意下载一个图片,放到项目中的static文件夹下面,然后可以使用下面的引入方法进行引用. <div <img src="~static/logo.png"></div> 这时候在npm run dev 下是完全正常的,那我们看一下打包. 打包静态HTML并运行 用Nuxt.js制作完成后,你可以打包成静态文件并放在服务器上,进行运行. 在终端中输入: npm run generate 然后在dist文件夹下输入live-serve

  • 打包非 JavaScript 静态资源详情

    目录 1.打包工具中的自定义导入 2.浏览器和打包工具中通用的导入语法 3.模棱两可的相对URL 4.工具链中的支持 4.1 打包工具 5. WebAssembly 5.1  通过Emscripten编译的C/C++ 5.2 通过 wasm-pack / wasm-bindgen 编译的 Rust 6.未来的导入方式 6.1  import.meta.resolve 6.2 导入断言 7.小结 8.参考资料 本文翻译自 https://web.dev/bundling-non-js-resour

  • Vue打包后访问静态资源路径问题

    Vue介绍中static文件夹里放的是静态资源目录,如图片.字体等. 我们发现运行npm run start后本地图片路径是没问题的,但是打包上传后会怎么样呢? 我们知道,执行npm run build 后会生成dist文件夹,把里面的index.html在浏览器运行会发现 图片路径出错啦!!! 解决方法: 1.先找到config下的index.js文件 把最后的'/' 改为 './' 2.接着把图片地址改为 以上就是Vue打包后访问静态资源路径问题的相关知识点,感谢大家的阅读和对我们的支持.

  • vue项目打包为APP,静态资源正常显示,但API请求不到数据的操作

    Vue项目通过Hbuild打包为APP后,静态文件正常显示,但并没有像开发时那样请求到数据. 这是为什么?因为APP并没有跨域,不存在跨域一说. 我们在开发的时候,js在不同的域之间进行数据传输或通信,所以会给项目设置代理来跨域 config下的index.js 比如这个 proxyTable: { '/api':{ target: 'http://XXX/xxx/v3', changeOrigin: true, pathRewrite: { '^/api': '' } } } 在开发时这样做是

  • vue填坑之webpack run build 静态资源找不到的解决方法

    vue cli搭建的项目,在本地测试调试都OK,运行npm run dev之后运行正常,今天放到服务器上跑,结果RD说找不到打包后的静态资源,浏览器控制台错误代码404 问了RD,因为服务器上线方式的调整,不会指定具体项目路径因此,https://bigdata.yiche.com/static/css/app.149f36018149fcbe537f02cafdc6f047.css 这个文件找不到,看看我们正常打包好的目录: 正确的访问路径是:https://bigdata.yiche.com

  • 解决vue打包之后静态资源图片失效的问题

    1.问题描述 在项目开发中,当我们通过npm run build打包之后将文件放在服务器上时通常会出现图片失效问题,控制台中提示某个图片没有找到(404错误),这些图片可以是以src引入的图片, 也可以是css中定义的背景图片.图片能否显示与你的静态资源文件存在位置和引入的路径直接相关,下面是我的其中一个项目的文件存放以及路径书写方式! 2.解决方法之一 静态资源static存放位置放在src目录下 你可能会问为什么放在src目录下?放在跟src同级目录下不可以吗?好吧,一开始我也是放在src同

  • vue-cli与webpack处理静态资源的方法及webpack打包的坑

    通过Vue-cli进行webpack打包的坑 Vue-cli为Vue项目搭建的脚手架的确很方便,但打包时容易出现空白页,或者对应的静态资源加载不了. 我是通过将项目/config下的index.js的assetsPublicPath变成'./',变成相对路径,进行解决. cd vue demo npm run dev //运行程序 npm run bulid //webpack打包 处理静态资源 你也许会注意到vue-cli与webpack结合的项目中,我们通常会有两个静态资源的路径:src/a

  • vue-cli脚手架打包静态资源请求出错的原因与解决

    问题 vue-cli默认配置打包后部署到特定路径下静态资源路径错误问题. 静态资源打包使用相对路径后css文件引入大图片路径错误问题 使用vue-cli2脚手架生成的默认打包配置文件,npm run build打包,部署项目到特定路径下://ip:port/test/index.html 此时访问//ip:port/test/index.html可以正常访问,但是引用的js和css等文件服务器响应为404,此时我们查看资源请求路径: http://ip:port/static/css/app.[

  • vue项目之webpack打包静态资源路径不准确的问题

    目录 webpack打包静态资源路径不准确 问题 静态资源找不到如js文件 图片找不到 webpack中的静态资源处理 Webpacked 资源 资源处理规则 在JavaScript里获取资源路径 "真实的" 静态资源 webpack打包静态资源路径不准确 问题 1.将打包好的项目部署到服务器,发现报错说图片找不到. 2.静态资源如js访问不到 分析并且解决问题 明确一点的就是,看到报错404,找不到静态资源,很明显,路径错误了. 静态资源找不到如js文件 资源打包路径有误,打包后的资

  • javascript实现动态导入js与css等静态资源文件的方法

    本文实例讲述了javascript实现动态导入js与css等静态资源文件的方法.分享给大家供大家参考.具体实现方法如下: /** * 动态导入静态资源文件js/css */ var $import = function(){ return function(rId, res, callback){ if(res && 'string' == typeof res){ if(rId){ if($($('#' + rId), $('head')).length>0){ return; }

  • 详解vue静态资源打包中的坑与解决方案

    本文主要解决 ①.vue-cli默认配置打包后部署至特定路径下静态资源路径错误问题; ②.静态资源打包使用相对路径后css文件引入大图片路径错误问题. 1.问题 vue-cli 脚手架生成的默认打包配置文件情况下运行 npm run build 打包后,部署项目至特定路径下:如: //ip:port/public/springActivity/ 此时访问: http://ip:port/public/springActivity/index.html index.html 可以正常访问,但是引用

随机推荐