esbuild 非??焖俚?web 打包器,使用 go 語(yǔ)言編寫(xiě)。
?? 特點(diǎn):
- 無(wú)需緩存也能很快速的編譯打包。
- 內(nèi)置 js、css、ts、jsx 類型文件編譯。
- 支持 es6 和 commonjs 模塊。
- 可以編譯打包成 esm 模塊和 common JS 模塊
- tree shaking 搖樹(shù)優(yōu)化、優(yōu)化資源大小、source-map 代碼映射
- 啟動(dòng)本地服務(wù),在監(jiān)聽(tīng)模式下文件發(fā)生變化重新編譯。
esbuild-vu3 代碼倉(cāng)庫(kù)地址文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-427501.html
安裝使用
創(chuàng)建示例項(xiàng)目
$> mkdir esbuild-vue3
$> cd esbuild-vue3
安裝 esbuild
$> npm init -y
$> npm install --save-exact esbuild vue@next
定義基礎(chǔ)的構(gòu)建腳本package.json
-
--bundle
打包編譯文件,可以將任何依賴項(xiàng)都內(nèi)聯(lián)到文件中。 -
--outfile
定義輸出文件名。多文件入口時(shí)則需要配置 outdir
{
"scripts": {
"build": "esbuild ./src/index.js --bundle --outfile=./dist/index.js"
},
"dependencies": {
"esbuild": "0.17.8",
"vue": "^3.2.47"
}
}
在index.js
中基本的輸出 vue 版本。
import { createApp } from "vue";
const app = createApp();
console.log(app.version);
npm run build
編譯后執(zhí)行編譯包node dist/index.js
可以看到打印出來(lái)的 vue 版本號(hào)。
編寫(xiě) build 腳本文件
像這種簡(jiǎn)單的執(zhí)行編譯構(gòu)建,可以直接書(shū)寫(xiě) esbuild --**
,實(shí)際項(xiàng)目中需要更多的配置。
創(chuàng)建 scripts/build.js
/**
* 編譯打包構(gòu)建項(xiàng)目
*/
require("esbuild")
.build({
// 編譯入口
entryPoints: ["src/index.js"],
//
bundle: true,
// 編譯輸出的文件名
// outfile: "out.js",
// 編譯文件輸出的文件夾
outdir: "dist",
})
.catch(() => process.exit(1));
在控制臺(tái)測(cè)試node scripts/build.js
正常,更新 package.json 中的腳本文件。
兩大常用 API build和transform
其他的一些 API 配置項(xiàng)有的只用于 build,有的只用于 transofrm,也有都可以用的。
-
build
打包編譯代碼,并寫(xiě)入文件系統(tǒng)。 -
transform
顧名思義,用于轉(zhuǎn)換代碼。比如.vue 文件轉(zhuǎn)換、typescript 轉(zhuǎn) js 等等。
build(options)
一個(gè)最簡(jiǎn)單的示例。
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/index.js"],
bundle: true,
outdir: "dist",
});
入口為當(dāng)前目錄的 index.js。打包編譯后輸出到 dist 文件目錄中。
在我們正常開(kāi)發(fā)時(shí),則需要監(jiān)聽(tīng)文件的變化,重新編譯。以及一個(gè)開(kāi)發(fā)時(shí)的文件服務(wù)器。
-
watch mode
監(jiān)視文件系統(tǒng),在編輯和保存的時(shí)候重新編譯。const esbuild = require("esbuild"); async () => { let context = await esbuild.context({ ...BaseConfig, sourcemap: "both", metafile: true, }); // 使用上下文,開(kāi)啟監(jiān)聽(tīng) await context.watch(); };
-
serve mode
開(kāi)發(fā)的同時(shí)則需要靜態(tài)資源服務(wù)器,以方便我們?cè)跒g覽器中看到更改的變化const esbuild = require("esbuild"); async () => { let context = await esbuild.context({ ...BaseConfig, sourcemap: "both", metafile: true, }); // 使用上下文,開(kāi)啟監(jiān)聽(tīng) await context.watch(); // 開(kāi)啟一個(gè)服務(wù) let { host, port } = await context.serve({ servedir: "dist", port: 8080, host: "127.0.0.1", }); console.log(`Serve is listening on http://${host}:${port}`); };
通過(guò)指定資源服務(wù)目錄,就可以啟動(dòng)一個(gè)靜態(tài)的資源服務(wù)器。搭配
watch mode
就可以支撐我們?nèi)粘5拈_(kāi)發(fā)模式了。 -
rebuild mode
手動(dòng)重新編譯,這個(gè)可以作為集成到其他構(gòu)建工具一起時(shí),可以手動(dòng)進(jìn)行編譯。await context.rebuild();
transform(code,options)
轉(zhuǎn)換代碼,將 JS 語(yǔ)法糖,轉(zhuǎn)換為瀏覽器可識(shí)別的 JS 原生代碼。也包括 css 預(yù)編譯 less、scss 等。
假設(shè)我們現(xiàn)在有一個(gè)用于轉(zhuǎn)換 .vue
文件的庫(kù),可以讀取到某個(gè)文件夾下的.vue 文件然后轉(zhuǎn)換
const esbuild = require("esbuild");
const fs = require("fs");
async () => {
// 讀取.vue文件
const contents = await fs.promises.readFile("src/App.vue", "utf8");
// 手動(dòng)執(zhí)行轉(zhuǎn)換
const result = await esbuild.transform(contents, {
loader: "vue-loader",
});
};
這個(gè) loader
配置稍后再將,假設(shè)暫時(shí)有這個(gè)一個(gè)解析 vue 文件的 loader。大概就是這個(gè)樣子
async\sync
同步、異步 API
同步、異步 API 都可以在特定的場(chǎng)景下使用。
- 同步 API 和插件一起使用,插件是異步的。
- 同步 API 會(huì)阻塞線程,所以需要更好的性能表現(xiàn),使用異步 API。
- 同步 APi 調(diào)用可以使你 的代碼看起來(lái)更整潔。在
async...await...
可用時(shí),我更喜歡用異步
import * as esbuild from "esbuild";
// 異步
let result1 = await esbuild.transform(code, options);
let result2 = await esbuild.build(options);
// 同步
let result1 = esbuild.transformSync(code, options);
let result2 = esbuild.buildSync(options);
API 配置項(xiàng)說(shuō)明
標(biāo)注說(shuō)明哪些可以用 build,哪些可以用 transform。(我閱讀過(guò)覺(jué)得重要的,還有一些未列出)
僅適用于build
-
entryPoints
編譯入口,字符串是為單入口,多入口時(shí)配置為數(shù)組形式 -
bundle
打包文件,從入口文件開(kāi)始,遞歸處理以來(lái)的文件,以內(nèi)聯(lián)的方式打包打包到一個(gè)文件中。 -
cancel
取消編譯進(jìn)程,context.cancel()
中斷打包。 -
watch
監(jiān)聽(tīng)文件系統(tǒng),發(fā)生變化可重新構(gòu)建。 -
serve
創(chuàng)建一個(gè)靜態(tài)資源服務(wù)。 -
rebuild
手動(dòng)調(diào)用,重新執(zhí)行打包。 -
tsconfig
配置 ts 的配置文件,默認(rèn)項(xiàng)目目錄下的tsconfig.json
-
tsconfigRaw
可以在直接傳遞 ts 時(shí)配置選項(xiàng),不用制定配置文件。 -
stdin
作為打包入口,可以手動(dòng)書(shū)寫(xiě)內(nèi)容。 -
splitting
代碼分割,只適用于format:esm
. -
assetNames
資源配置輸出路徑、 -
chunkNames
分包配置輸出塊文件的文件路徑 -
outdir
輸出文件目錄名 -
outfile
輸出文件名,只適用于單入口 -
alias
為一些長(zhǎng)路徑配置別名 -
external
定義構(gòu)建時(shí)不被處理的包。引入外部包,cdn 引入等 -
inject
定義全局變量的替換文件。 -
metafile
打包時(shí)生成一些元數(shù)據(jù)信息,可用于分析打包后的代碼。
僅適用于transform
沒(méi)有
同時(shí)適用build、transform
-
platform
代碼生成面向的平臺(tái),默認(rèn)瀏覽器browser
,可以指定為node、neutral
-
loader
指定文件該如何解析,根據(jù)文件后綴指定。 -
banner
可以自定義內(nèi)容插入到文件頂部。 -
footer
可以自定義內(nèi)容插入到文件尾部 -
charset
配置打包的字符集,默認(rèn)是ASCII
-
format
配置輸出文件的格式,包括 iife、cjs、esm -
jsx
jsx 語(yǔ)法解析的配置 -
jsxFactory
自定義 jsx 語(yǔ)法如何解析,定義函數(shù)名。vue 中是h
-
target
構(gòu)建目標(biāo)代碼生成的環(huán)境,比如chrome\edge\node
,并可指定版本 -
define
自定義一些全局變量,以便在不同構(gòu)建模式中,有不同的表現(xiàn) -
drop
打包時(shí),丟棄掉代碼中指定的語(yǔ)句,比如 debugger、console -
minify
最小化代碼 -
treeShaking
搖樹(shù)優(yōu)化 -
sourcemap
代碼映射文件生成,代碼瀏覽器調(diào)試。
配置 vue
創(chuàng)建 App.vue
,并修改 index.js. 在此編譯時(shí)提示報(bào)錯(cuò)No loader is configured for ".vue" files: src/App.vue
安裝vue-loader
$> npm i vue-loader -D
配置build.js
, 增加 loader 配置,針對(duì)文件后綴指定文件解析方式。
require("esbuild").build({
// ...
// 配置loader
loader: {
".vue": "vue-loader",
},
});
配置完成后,在此執(zhí)行npm run build
,雖然不報(bào)錯(cuò)了,但是編譯文件并沒(méi)有生成,可以看到控制臺(tái)當(dāng)前命令執(zhí)行失敗的。但是看不到日志
配置打包日志輸出,調(diào)整build.js
/**
* 編譯打包構(gòu)建項(xiàng)目
*/
const esbuild = require("esbuild");
// 開(kāi)發(fā)、生產(chǎn)環(huán)境公用配置
const BaseConfig = require("./base.js");
(async () => {
let result = await esbuild.build({
...BaseConfig,
// 壓縮代碼
minify: true,
// 配合壓縮移除空格
minifyWhitespace: true,
// 配合壓縮重命名變量
minifyIdentifiers: true,
metafile: true,
});
let text = await esbuild.analyzeMetafile(result.metafile, {
verbose: true,
});
console.log(text);
})();
重新執(zhí)行 npm run build
,這時(shí)候看到了打印的錯(cuò)誤輸出 Invalid loader value: "vue-loader"
看來(lái)是配置錯(cuò)誤,不是這樣配置的。??
后來(lái)研究了好久,想利用 @vue/compiler-sfc
寫(xiě)一個(gè) esbuild 插件,一直沒(méi)有調(diào)試通,暫時(shí)放棄。
安裝插件 esbuild-plugin-vue3
通過(guò)查找已經(jīng)有人寫(xiě)好的插件供使用
$> npm i esbuild-plugin-vue3
調(diào)整基礎(chǔ)腳本配置文件base.js
const vuePlugin = require("esbuild-plugin-vue3");
module.exports = {
// 插件
plugins: [vuePlugin()],
};
再次執(zhí)行啟動(dòng),運(yùn)行成功。
這個(gè)插件支持生成 html 文件,并可以把生成 css 文件注入到視圖中。
module.exports = {
// 插件
plugins: [
vuePlugin({
generateHTML: "public/index.html",
}),
],
};
遇到的一寫(xiě)問(wèn)題:
-
alias 定義的'@'在插件中不能解析。提示文件不存在。是因?yàn)樗麤](méi)有轉(zhuǎn)換
@
。配置
@
的時(shí)候,需要解析當(dāng)前腳本所在的路徑,/scripts/dev.js
. 配置為path.resolve(__dirname, "../src")
使用 jsx 語(yǔ)法
重新創(chuàng)建了App.jsx
文件,和 App.vue 內(nèi)容一致。導(dǎo)入使用,報(bào)錯(cuò)React is not defined
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
name: "admin",
num: 0,
};
},
mounted() {
console.log("App init");
},
render() {
return (
<div class="app">
<h1>{this.name}</h1>
<p>{this.num}</p>
<button onClick={() => this.num++}>click++</button>
</div>
);
},
});
在 esbuild 中,默認(rèn) jsx 語(yǔ)法解析是使用的 react 庫(kù),所以沒(méi)有安裝 react 就會(huì)報(bào)錯(cuò)。修改配置,使用自定義 jsx 解析函數(shù)
module.exports = {
loader: {
".js": "jsx",
},
jsxFactory: "h",
jsxFragment: "Fragment",
};
順便配置.js 文件是被 jsx 語(yǔ)法解析,這樣文件后綴直接書(shū)寫(xiě) App.js。
現(xiàn)在重新運(yùn)行,還是會(huì)報(bào)錯(cuò),報(bào)錯(cuò)h is not defined
. 雖然定義了,但是沒(méi)有指明函數(shù)從哪里來(lái)。
修改App.js
文件,增加導(dǎo)入h
函數(shù)
import { h } from "vue";
再次運(yùn)行,頁(yè)面正常打開(kāi)。
但有個(gè)問(wèn)題,我們需要在每個(gè)頁(yè)面都要導(dǎo)入import { h } from "vue";
就感覺(jué)比較麻煩。
可以通過(guò)屬性inject
注入來(lái)定義 h 函數(shù),從而達(dá)到自動(dòng)注入的目的。
新建一個(gè)jsxFactory.js
文件,定義導(dǎo)出函數(shù)。
const { h, Fragment } = require("vue");
export { h as "React.createElement", Fragment as "React.Fragment" };
重新修改配置文件,這時(shí)使用了注入文件修改了全局函數(shù)React.createElement
,就不需要再配置 jsxFactory 了。
module.exports = {
// jsxFactory: "h",
// jsxFragment: "Fragment",
inject: ["libs/jsxFactory.js"],
};
現(xiàn)在可以開(kāi)心的移除 App.js 中 h 函數(shù)的導(dǎo)入了。后續(xù)的文件也需要在配置。
使用 less
安裝less
,即可正常使用
$> npm i less -D
但是單獨(dú)引入.less 文件時(shí),提示報(bào)錯(cuò),沒(méi)有解析該文件的 loader。
安裝esbuild-plugin-less
,
const { lessLoader } = require("esbuild-plugin-less");
module.exports = {
// 插件
plugins: [lessLoader()],
};
周邊組件庫(kù)安裝
axios\vue-router\vuex\element-plus
安裝
$> npm i axios dayjs element-plus vue-router vuex
錯(cuò)誤Cannot use import statement outside a module
解析問(wèn)題
一些分包 chunk 還存在 import??赡苁?es、cjs 混合導(dǎo)致無(wú)法被轉(zhuǎn)義。
基礎(chǔ)配置中,打包輸出格式format:esm
, 支持分包配置splitting
,可根據(jù) imort 動(dòng)態(tài)導(dǎo)入的打包依賴項(xiàng)。
修改配置,移除分包配置。使用iife\cjs
模式編譯輸出項(xiàng)目訪問(wèn)正常。
module.exports = {
format: "iife",
// splitting: true,
};
使用
esm
進(jìn)行分包編譯時(shí),存在一個(gè)包里沒(méi)有 import 語(yǔ)句。其他分包都有,報(bào)錯(cuò)不能使用。
有 babel 插件轉(zhuǎn)成 es5 應(yīng)該就可以了
解決 format:'esm'
分包前端報(bào)錯(cuò)問(wèn)題,也就是上面提到的問(wèn)題
在使用了 esModule 采取模塊分包后,所有的語(yǔ)法比如import、let、const
新語(yǔ)法都是支持的。我嘗試通過(guò)配置構(gòu)建目標(biāo)而不使用這些特性語(yǔ)法。
module.exports = {
// 構(gòu)建目標(biāo)es新標(biāo)準(zhǔn)
target: ["es5"],
};
再次編譯控制之態(tài)報(bào)錯(cuò),全是語(yǔ)法不被支持。也就說(shuō)明了 esbuild 只是一個(gè)編譯打包器,想要轉(zhuǎn)義這些語(yǔ)法,還得使用 babel。
自動(dòng) polyfill 注入不在 esbuild 的范圍內(nèi)
那我們還是使用最新的語(yǔ)法支持,構(gòu)建目標(biāo)。為了讓瀏覽器支持 import 模塊導(dǎo)入,需要在引入的所有 script 腳本中增加type='module'
之前使用插件esbuild-plugin-vue3
,生成了 index.html。查了配置沒(méi)有地方配置給 script 增加 type。
module.exports = {
vuePlugin({ generateHTML: "public/index.html" }),
};
所以不使用生成的 index.html,去掉配置參數(shù)generateHTML
。先使用public/index.html
測(cè)試,待npm run start
后, 更改 index.html,手動(dòng)導(dǎo)入主入口文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>esbuild+vue3</title>
<link rel="stylesheet" href="../dist/index.css" />
</head>
<body>
<div id="app"></div>
<script type="module" src="../dist/index.js"></script>
</body>
</html>
使用 vscode 的 live serve
插件功能起一個(gè)靜態(tài)服務(wù)。訪問(wèn)正常,這樣就給了一個(gè)思路,只需要復(fù)制public/index.html
,手動(dòng)導(dǎo)入主入口文件即可。
不能處理vue-router
組件動(dòng)態(tài)導(dǎo)入的問(wèn)題,
vue-router
支持開(kāi)箱即用的動(dòng)態(tài)導(dǎo)入,這樣可以將代碼分隔成不同的代碼塊?,F(xiàn)在的配置不能處理,可能需要配置 babel,額外處理了。
import MainPage from "../views/index.vue";
onst routes = [
{
path: "/",
redirect: "/main",
component: MainPage,
// component: () => import("../views/index.vue"),
}
]
安裝了一個(gè)esbuild-plugin-babel
來(lái)配置使用 babel, 但因?yàn)椴皇?commonJS 規(guī)范的,導(dǎo)致不能導(dǎo)入使用。
import babel from "esbuild-plugin-babel";
// 需要修改package.json 中配置,
// type:'module'
// 這樣就會(huì)導(dǎo)致在腳本中無(wú)法使用require,無(wú)法使用其他插件。沖突更多了。
解決package.json配置type:module時(shí)的問(wèn)題
這個(gè)問(wèn)題和上面的問(wèn)題牽扯,單獨(dú)提取是因?yàn)樾薷牡谋容^多。
找一個(gè)自動(dòng)生成index.html
的插件,并可以自動(dòng)加載主入口文件。@chialab/esbuild-plugin-html
這個(gè)有點(diǎn)意思,當(dāng)然還有其他的插件,之后嘗試,
安裝@chialab/esbuild-plugin-html
這個(gè)插件的package.json
配置屬性 type 就是 module
。說(shuō)明僅支持 esm,也就需要修改所有的腳本文件,不能再以 cjs 方式加載了。
修改了type:module
就表明所有的 js 文件都是 esModlue,也就不能使用require\module.exports
語(yǔ)句了。
這個(gè)插件將提供的index.html
作為入口文件,然后將編譯過(guò)后的入口文件和 css 樣式文件動(dòng)態(tài)加載到 html 中。
所有的構(gòu)建路徑都變得無(wú)法捉摸。
修改配置,原來(lái)的 html 模板是放到 public 下的,配置并不能起作用,不能加載到 ./src/index.js
主入口文件。
看了示例,是放到 src 下的,也就是和入口文件同目錄,我放到項(xiàng)目根目錄下。
這讓我想起了 vite 要求 index.html 在項(xiàng)目根目錄下。
修改index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>esbuild+vue3</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/index.js"></script>
</body>
</html>
修改編譯配置文件scripts/base.js
import html from "@chialab/esbuild-plugin-html";
export default {
entryPoints: ["./index.html"],
// 資源目錄文件路徑
assetNames: "assets/[name]-[hash]",
// 分包資源路徑
chunkNames: "[ext]/[name]-[hash]",
// 打包輸出的格式
format: "esm",
// 代碼分離,一是多入口共享文件;二是import動(dòng)態(tài)導(dǎo)入的依賴項(xiàng)
splitting: true,
plugins: [
// ...
html(),
],
alias: {
"@": path.resolve("./src"),
},
};
雖然我們的腳本路徑是scripts/base.js
,腳本中相對(duì)路徑引用確實(shí)./
,而不是../
。esm 和 cjs 上下文不同導(dǎo)致的。
我們?cè)陧?xiàng)目根目錄下執(zhí)行的腳本npm run start
,在 esm 中,一直保持這種上下文狀態(tài)。所以都是./src,./index.html
使用此插件是,必須配置chunkNames/assetNames
,指定資源編譯目錄。才可以正常加載。
腳本文件中導(dǎo)入需改為 esm,記錄一下其他解決的問(wèn)題
-
__dirname
是 node 環(huán)境下的特殊變量,現(xiàn)在改為 esm,是不能用了。只能依賴 node 庫(kù)
// scripts/base.js
export default {
// alias: {
// "@": path.resolve(__dirname, "../src"),
// },
alias: {
"@": path.resolve("./src"),
},
};
-
esbuild-plugin-vue3
插件不能用了,只支持 require 加載, 安裝esbuild-plugin-vue-next
-
解決 jsx 語(yǔ)法的語(yǔ)法不被支持了,這個(gè)很奇怪
Using a string as a module namespace identifier name is not supported in the configured target environment ("es2020")
// libs/jsxFacotry.js
const { h, Fragment } = require("vue");
// export { h as "React.createElement", Fragment as "React.Fragment" };
window.React = {
createElement: null,
Fragment: null,
};
window.React.createElement = h;
window.React.Fragment = Fragment;
突然發(fā)現(xiàn)只要定義全局變量命名覆蓋就好了。
- esm 和 cjs 腳本相對(duì)路徑上下文不同。
發(fā)現(xiàn)其他插件
-
json \ css \ text
文件都是默認(rèn)支持導(dǎo)入,無(wú)需配置,當(dāng)然也可以配置為其他 loader 組件。 -
圖片資源
.png\jpg
等需要手動(dòng)配置導(dǎo)入的 loader,可選多種方式,
-
binary
二進(jìn)制文件,需要操作二進(jìn)制文件時(shí)。打包時(shí)將編碼嵌入到編譯包。 -
base64
加載為 base64,將編碼作為字符串嵌入到編譯包。 -
dataurl
加載為二進(jìn)制數(shù)據(jù),作為 base64 編碼嵌入到編譯包。 -
file
將文件輸出到輸出目錄中,使用文件名默認(rèn)導(dǎo)出進(jìn)行導(dǎo)入。 -
copy
復(fù)制文件到編譯目錄中,重寫(xiě)導(dǎo)入路徑。引用該文件路徑,module.exports = { // 配置loader loader: { ".png": "file", }, };
- 配置 babel,以便使用代碼拆分功能,以及路由的動(dòng)態(tài)導(dǎo)入。
可以關(guān)注倉(cāng)庫(kù)分支,有時(shí)間會(huì)完善 babel 的配置。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-427501.html
esbuild-vu3 代碼倉(cāng)庫(kù)地址
到了這里,關(guān)于初識(shí)esbuild、構(gòu)建vue3腳手架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!