使用Svelte開發Chrome Extension

語言: CN / TW / HK

       

一、背景

起因最近Chrome瀏覽器升級到96大版本後,二維碼入口從位址列移動至二級選單。這對H5前端開發來說不太友好,每次需要頁面二維碼時都需要多點兩下(* ̄︿ ̄)。

因此萌生了開發一個二維碼Chrome Extension的想法(@ ̄ー ̄@)。

經過多方技術選型(React、原生、Vue、Svelte等),最終選擇 Svelte [1] ,原因是

  • 語法簡單,心智負擔小

  • 執行時程式碼少,打包體積小

  • 響應式

d=====( ̄▽ ̄*),接下來就開始Svelte × Chrome Extension之旅。

二、建立&開發

2.1 專案建立

2.1.1 專案初始化

使用 Svelte Kit [2] 新建專案 npm init [email protected] qrcode-extension ,目錄結構如下:

  • src :原始檔目錄

    • lib :元件庫等
    • routes :約定式路由檔案
    • app.html :入口模板檔案
  • static :靜態檔案目錄
  • svelte.config.js :svelte配置

初始化專案之後可以直接 npm run dev 啟動。

2.1.2 支援外掛開發

  1. manifest檔案

Extensions are built on web technologies such as HTML, JavaScript, and CSS.

—— Chrome開發文件 [3]

Chrome外掛本質上是以 manifest.json 為入口規定的一系列前端資源集合,基於Chrome瀏覽器提供的API,實現各種功能。

因此在專案的靜態資原始檔目錄中新增 manifest.json 檔案:

{
"name": "QrCode",
"description": "A simple qrcode extension powered by Svelte",
"version": "1.0",
"manifest_version": 3,
"permissions": ["tabs", "downloads"],
"action": {
"default_popup": "index.html"
},
"icons": {
"16": "/images/qrcode-16.png",
"32": "/images/qrcode-32.png",
"48": "/images/qrcode-48.png",
"128": "/images/qrcode-128.png"
}
}

幾個比較重要的欄位:

MV3檔案格式參考 [4]

  • manifest_version :manifest版本,之前為Manifest V2(MV2),Chrome推薦使用Manifest V3(MV3)
  • permissions :擴充套件要使用的瀏覽器許可權,大部分 Chrome擴充套件API [5] 均有許可權依賴
  • action :定義外掛操作行為對應的頁面

    • default_popup :點選外掛圖示時的頁面
  • icons :外掛圖示
  1. 新增chrome型別定義

安裝 @types/chrome 到devDependencies,並在 tsconfig.json#compilerOptions#types 中新增chrome型別。

2.2 功能開發

2.2.1 需求拆分

參考Chrome瀏覽器二維碼功能:

2.2.2 連結展示

需要獲取Chrome瀏覽器當前開啟的tab,查閱 開發文件 [6] 可知對應API為 chrome.tabs ,並在 manifest.json#permissions 新增tabs許可權宣告。

在首頁載入時,獲取當前tab的url,url展示到輸入框,並作為二維碼元件的輸入屬性。

async function getCurrentTab() {
if (typeof chrome === 'undefined' || typeof chrome.tabs === 'undefined') {
return { url: '' };
}
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}

import { onMount } from 'svelte';

let url = '';
// get current tab's url
onMount(() => {
(async () => {
const tab = await getCurrentTab();
url = tab.url || '
';
})();
});

2.2.3 Svelte元件

二維碼元件程式碼定義在 libs/QrCode.svelte 中。

  1. 元件程式碼

Svelte與vue類似,提供單檔案元件。包括三部分:

  • <script></script> :js/ts業務邏輯
  • html:元件html模板

  • <style></style> :css樣式,編譯時自動做樣式隔離

元件詳細程式碼如下所示,使用qrcode庫生成二維碼。

<script lang="ts">
import { toDataURL } from 'qrcode';
import type { QRCodeToDataURLOptions } from 'qrcode';
import { writable } from 'svelte/store';
import { createEventDispatcher } from 'svelte';

// 輸入屬性
export let text;
export let option: QRCodeToDataURLOptions = {
type: 'image/png',
margin: 2,
width: 240
};

const dataUrl = writable('');
const dispatch = createEventDispatcher();

// 響應式
$: {
if (text) {
toDataURL(text, option).then((url) => {
dataUrl.set(url);
// 派發元件事件
dispatch('ready', { url });
});
} else {
dataUrl.set('');
}
}
</script>

<div class="qrcode">
{#if $dataUrl}
<img src={$dataUrl} alt="qrcode">
{/if}
</div>

<style>
.qrcode {
width: 240px;
height: 240px;
border: 2px solid #e8eaed;
border-radius: 10px;
background: #f1f3f4;
}

img {
width: 100%;
height: auto;
border-radius: 10px;
}
</style>
  1. 生命週期

  • onMount:元件已掛載到DOM上(SSR時不執行)

  • beforeUpdate:元件狀態變更時立即執行,第一次會在onMount之前執行

  • afterUpdate:元件更新後

  • onDestroy:元件解除安裝(如onMount返回函式,則會執行)

  1. 輸入/輸出

元件中通過 export 宣告輸入屬性。

export let text;
export let option: QRCodeToDataURLOptions = {
type: 'image/png',
margin: 2,
width: 240
};

使用 createEventDispatcher 建立事件,當生成二維碼圖片base64時,觸發ready事件。

import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

dispatch('ready', { url });
  1. 響應式

參考: svelte響應式程式碼塊 [7]

利用label語法宣告響應式邏輯,當輸入屬性text變化時更新二維碼內容。

$: {
if (text) {
toDataURL(text, option).then((url) => {
dataUrl.set(url);
dispatch('ready', { url });
});
} else {
dataUrl.set('');
}
}

2.2.4 二維碼下載

下載對應的Chrome API為 chrome.downloads ,同樣在 manifest.json#permissions 新增downloads許可權宣告。

監聽二維碼元件ready事件,並更新dataUrl。點選下載按鈕時觸發二維碼下載。

function downloadQrCode() {
if (!dataUrl) {
return;
}

chrome.downloads?.download({
url: dataUrl,
filename: getFilename(url),
});
}

<button class="qrcode-download" on:click={downloadQrCode}>下載</button>

三、除錯&構建

3.1 構建

Before you can deploy your SvelteKit app, you need to adapt it for your deployment target. Adapters are small plugins that take the built app as input and generate output for deployment.

—— Svelte Adapter [8]

Svelte使用adapter轉換編譯產物,預設提供的adapter是 @sveltejs/adapter-auto ,需要配置打包目標平臺(Vercel、Cloudflare、Netlify)。一般使用 @sveltejs/adapter-static 打包靜態產物。

預設打包路徑為 build ,static資料夾下的靜態資源會打包到根路徑。

但用Chrome載入這個產物作為外掛會報錯,

ERROR Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-ri/Klr/GKqsTbCFK6rSYKj7VDIccXQJeipKxBmqg69g='), or a nonce ('nonce-...') is required to enable inline execution.

原因是違反CSP策略, index.html 使用了內聯script指令碼,而這個檔案是打包時動態生成的,錯誤中提到的unsafe-inline關鍵字或nonce-等方法在MV3中均不支援。

解決方案編譯後匹配所有html檔案中的內聯指令碼,將內聯指令碼內容寫入js檔案,並在html檔案中替換為sctipt標籤。自定義adapter完成產物轉換操作。

import type { Adapter } from '@sveltejs/kit';
import * as glob from 'fast-glob';
import { readFileSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';

interface Props {
/** dest for extension package dir */ dest: string;
}

function uuid() {
return Math.random().toString(36).slice(2);
}

function handleHtml(htmlPath, scriptTag) {
const html = readFileSync(htmlPath).toString();
const matchReg = /<script\b[^>]*>([\s\S]*)</script>/gm;
const result = matchReg.exec(html);

return result && result[1]
? {
html: html.replace(matchReg, scriptTag),
script: result[1],
}
: null;
}

export function extensionAdapter({ dest }: Props): Adapter {
return {
name: 'crx-adapter',

async adapt({ utils }) {
utils.rimraf(dest);

utils.copy_static_files(dest);
utils.copy_client_files(dest);
utils.rimraf(join(dest, '_app'));

await utils.prerender({ all: true, dest: dest });

const fileNames = await glob(join(dest, '**', '*.html'));
for (const fileName of fileNames) {
const dir = dirname(fileName);
const scriptFileName = `start-${uuid()}.js`;
const res = handleHtml(
fileName,
`<script type="module" src="/${scriptFileName}"></script>`,
);

if (res) {
writeFileSync(fileName, res.html);
writeFileSync(join(dir, scriptFileName), res.script);
}
}
},
};
}

3.2 除錯

產物打包好後,開啟Chrome瀏覽器,進入chrome://extensions/

  • 點選“載入已解壓的擴充套件程式”,選擇打包目錄以載入外掛

  • 當產物更新時,重新build並重新整理外掛即可

3.3 效果

最終效果如下,在網頁中點選外掛按鈕,即顯示對應二維碼。

對比一下:

Chrome自帶 qrcode-extension

四、總結

本文主要工作如下:

  • 使用Svelte開發二維碼Chrome Extension

  • 自定義Svelte Adapter適配Chrome外掛安全策略

參考資料

[1]

Svelte: http://svelte.dev/

[2]

Svelte Kit: http://kit.svelte.dev/

[3]

Chrome開發文件: http://developer.chrome.com/docs/extensions/mv3/overview/

[4]

MV3檔案格式參考: http://developer.chrome.com/docs/extensions/mv3/manifest/

[5]

Chrome擴充套件API: http://developer.chrome.com/docs/extensions/reference/

[6]

開發文件: http://developer.chrome.com/docs/extensions/reference/#stable_apis

[7]

svelte響應式程式碼塊: http://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive

[8]

Svelte Adapter: http://kit.svelte.dev/docs#adapters

:heart: 謝謝支援

以上便是本次分享的全部內容,希望對你有所幫助^_^

喜歡的話別忘了 分享、點贊、收藏 三連哦~。

歡迎關注公眾號 ELab團隊 收穫大廠一手好文章~

我們來自位元組跳動,是旗下大力教育前端部門,負責位元組跳動教育全線產品前端開發工作。

我們圍繞產品品質提升、開發效率、創意與前沿技術等方向沉澱與傳播專業知識及案例,為業界貢獻經驗價值。包括但不限於效能監控、元件庫、多端技術、Serverless、視覺化搭建、音視訊、人工智慧、產品設計與營銷等內容。

歡迎感興趣的同學在評論區或使用內推碼內推到作者部門拍磚哦

位元組跳動校/社招投遞連結:

http://job.toutiao.com/s/FtGqRb6

內推碼: 8BSWTYU