使用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 svelte@next 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: https://svelte.dev/

[2]

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

[3]

Chrome開發文檔: https://developer.chrome.com/docs/extensions/mv3/overview/

[4]

MV3文件格式參考: https://developer.chrome.com/docs/extensions/mv3/manifest/

[5]

Chrome擴展API: https://developer.chrome.com/docs/extensions/reference/

[6]

開發文檔: https://developer.chrome.com/docs/extensions/reference/#stable_apis

[7]

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

[8]

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

:heart: 謝謝支持

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

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

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

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

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

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

字節跳動校/社招投遞鏈接:

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

內推碼: 8BSWTYU