크롬 확장 개발 방법

22405 단어 htmlgithubjavascript
표지 사진 작성자SigmundUnsplash

TL;박사 01 명
나는 상당히 간단한 크롬 확장자를 '오류' 라고 개발하고 Github 에서 원본 코드를 공유했다.
기능을 이해하고 직접 시도하려면 Github 또는 watch this video의 지침을 따르십시오.
이제 왜 그리고 어떻게 이 점을 발전시켰는지 진일보한 설명을 드리겠습니다.

문제.
업무 중에 나는 자주 이런 사실을 만난다. 서로 다른 환경에서 같은 프로그램을 포함하는 여러 브라우저 옵션을 여는 것은 매우 위험하다.명백한 원인으로 인해, 당신은 생산 환경에서 테스트 조작을 실행하고 싶지 않습니다.
일부 SOM 방법은 이러한 상황을 피할 수 있는데 그 중에서 가장 흔히 볼 수 있는 방법은 환경 변수를 사용하여 일부 요소의 스타일을 설정하는 것이다.예를 들어, 프로덕션 환경의 탐색 모음이나 문서 본문의 배경색은 녹색이고 테스트 환경의 배경색은 빨간색입니다.
불행히도, 내가 현재 사용하고 있는 프로그램에는 이 기능이 없다.내가 거의 생산 환경에 대해 불필요한 조작을 한 후에 나는 이것이 QA라고 생각해서 해결 방안을 찾기 시작했다.
면책 성명: Angular나 React를 사용할 생각은 했지만 가치가 없다고 생각합니다.이 책은 나의 생활을 더욱 가볍게 할 수 있지만, 나는 그것에 만족하지 않아서, 나는 순수한 자바스크립트를 사용하기로 결정했다.이것은 내가 진정으로 필요로 하는 물건이기 때문에 나는 가능한 한 빨리 기능 버전을 가지고 싶다.

즉시 사용 가능: 세련된 디자인
내가 발견한 첫 번째 일은 Stylish이다.그것은 유행 사이트를 위해 맞춤형 스타일/주제를 선택할 수 있게 한다.그러나 자신의 스타일을 작성해서 특정 패턴에 맞는 URL에 적용할 수도 있다.
이것은 내가 어떤 웹 페이지의 맨 위에 사용자 정의 메시지를 표시할 수 있도록 유사한 것을 구축하려는 생각을 불러일으켰다.이 정보들은 나의 현재 업무 환경의 지시가 될 수 있다.

맞춤형 솔루션 시작
우리가 해야 할 첫 번째 일은 명세서를 작성하는 것이다.json.여기에 일반 응용 프로그램 정보와 일부 설정 기초를 설명합니다. { "name": "Mistake", "version": "1.0", "description": "Avoid disaster in production by displaying a message on pages that meet the criteria you define.", "permissions": ["webNavigation", "storage"], "content_scripts": [ { "matches": ["<all_urls>"], "js": ["content.js"], "run_at": "document_idle" } ], "manifest_version": 2, "options_page": "options.html" } 여기서 가장 중요한 것은 정확한 권한을 성명하는 것이다.예를 들어 Google Chrome에서 스토리지 API에 액세스해야 한다는 사실을 알려야 합니다.메시지와 상세한 정보를 저장하기 위해서, 우리는 이 정보를 저장할 곳이 필요하기 때문이다.
Chrome에서 탐색할 때마다 옵션 페이지에 설명된 규칙 중 하나에 맞는 페이지인지 확인하려면 웹Navigation API에 액세스해야 합니다.

상세 설명 옵션 페이지
다음에 우리는 옵션 페이지(options.html)에 들어갈 수 있다.이 페이지에서는 사용자가 특정 옵션을 정의할 수 있습니다.이 확장의 예를 살펴보자.
e, g. 사용자로서'이것은 당신의 로컬 환경입니다!'라는 메시지를 표시하고 싶습니다."https://localhost"로 시작하는 모든 URL에서
간단히 말해 다음과 같은 3가지 모드 일치 옵션을 제공합니다.
  • URL은
  • 로 시작합니다.
  • URL 포함
  • URL은
  • 로 끝납니다.
    정보의 다음 요소는 사용자 정의가 가능해야 합니다.
  • 텍스트 색상
  • 배경색
  • 텍스트
  • 확장에 대한 정보를 추가하고 새 규칙을 추가하기 위한 단추를 놓을 것입니다.그것은 아무것도 하지 않고 단지 견지해 나갈 뿐이다.마지막으로 스타일링에 편리하도록 부트를 CDN에서 로드합니다.
    옵션입니다.html <!DOCTYPE html> <html> <head> <title>Mistake - Options</title> <link rel="stylesheet" href="./css/bootstrap.min.css"> <style> h2 { margin: 2rem 0; } p { font-size: 1.5rem; } #add { margin-top: 2rem; font-size: 1.5rem; } .rule { border-bottom: 1px solid black; } .rule:last-of-type { border-bottom: none; } button[data-toggle="collapse"] { border: none; background-color: #fff; margin-top: 2rem; margin-bottom: 1rem; color: black; display:block; outline: none; font-weight: 600; font-size: 1.5rem; } button[data-toggle="collapse"]:hover, button[data-toggle="collapse"]:visited, button[data-toggle="collapse"]:active, button[data-toggle="collapse"]:focus { background-color: unset !important; color: unset !important; border: none; outline: 0 !important; outline-offset: 0 !important; background-image: none !important; -webkit-box-shadow: none !important; box-shadow: none !important; } .btn-light:focus, .btn-light.focus { box-shadow: 0; } input[type="color"] { display: block; border-radius: 50%; width: 50px; height: 50px; border: none; outline: none; -webkit-appearance: none; } input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } input[type="color"]::-webkit-color-swatch { border-radius: 50%; } </style> </head> <body style="padding-top: 5rem;"> <div class="container"> <h2>What does Mistake do?</h2> <p>Display a custom message at the top of any webpage that meets the criteria you define.</p> <h2>Why would I want to do such a thing?</h2> <p>Have you ever worked having <strong>multiple tabs of the same application</strong> open, but in <strong>different environments</strong>? Then you know how easy it is to live everyone's worst nightmare: screwing things up in production.</p> <p>After yet another near miss, I decided to take matters into my own hands and design this plug-in. Now, when I'm in production, at least I'm significantly reducing the odds of making a <i>Mistake</i>.</p> <h2>How does it work?</h2> <p>Start by adding a new rule using the button below. Add as many rules as you like.<br/> Now, whenever you open a tab with the URL that matches the pattern, your message will be displayed. Et voila!</p> <button type="button" class="btn btn-primary" id="add"> Add a new rule </button> <div id="rules" style="padding-top: 20px;"></div> </div> <script src="./js/jquery-3.5.1.slim.min.js"></script> <script src="./js/popper.min.js"></script> <script src="./js/bootstrap.min.js"></script> <script src="config.js"></script> <script src="helpers.js"></script> <script src="options.js"></script> </body> </html> 이제 Javascript 옵션 파일 (options.js) 에서 논리를 계속 작성할 수 있습니다.5가지 주요 기능으로 구성되어 있습니다.

  • initializeRules는 페이지를 불러올 때 저장소에서 기존 규칙을 가져오고 displayRules 함수를 사용하여 표시합니다.

  • createRule에는 옵션 페이지에 특정 규칙을 표시하는 데 사용되는 모든 HTML 및 CSS가 포함되어 있습니다.

  • saveRule은 규칙에 대한 정보를 스토리지에 저장하고 성공할 때 경고를 표시합니다.

  • removerule 규칙이 스토리지와 화면에서 제거됩니다.
  • 옵션입니다.js const buttonAddNewRule = document.getElementById("add"); const rulesList = document.getElementById("rules"); window.onload = function () { initializeRules(); buttonAddNewRule.addEventListener("click", createRule); rulesList.addEventListener("click", saveRule); rulesList.addEventListener("click", removeRule); }; function initializeRules() { chrome.storage.sync.get(null, function (syncItems) { displayRules(syncItems); }); } function displayRules(rules) { for (const value of Object.values(rules)) { createRule( value.type, value.expression, value.message, value.textColor, value.backgroundColor ); } } function createRule(type, expression, message, textColor, backgroundColor) { removeActiveAlert(); const newRule = document.createElement("div"); newRule.classList.add("rule", "pt-3"); newRule.setAttribute("data-index", getCurrentNumberOfRules()); const toggleButton = document.createElement("button"); toggleButton.classList.add("btn", "btn-light"); toggleButton.setAttribute("type", "button"); toggleButton.setAttribute("data-toggle", "collapse"); toggleButton.setAttribute("data-target", "#collapse" + getCurrentNumberOfRules()); toggleButton.setAttribute("aria-expanded", "false"); toggleButton.setAttribute("aria-controls", "collapse" + getCurrentNumberOfRules()); if (!type || !expression) { toggleButton.innerText = "New rule (unsaved)"; } else { toggleButton.innerHTML = `${type} "${expression}" ↓`; } const collapseDiv = document.createElement("div"); collapseDiv.classList.add("collapse", "show", "mb-5"); collapseDiv.setAttribute("id", "collapse" + getCurrentNumberOfRules()); const card = document.createElement("div"); card.classList.add("card", "card-body"); card.appendChild(createTypeButtonGroup(type)); card.appendChild(createExpressionInput(expression)); card.appendChild(createMessageInput(message)); card.appendChild(createColorInput("textColor", textColor)); card.appendChild(createColorInput("backgroundColor", backgroundColor)); card.appendChild(createButton("save")); card.appendChild(createButton("remove")); collapseDiv.appendChild(card); newRule.appendChild(toggleButton); newRule.appendChild(collapseDiv); rulesList.appendChild(newRule); } function saveRule(rule) { if (rule.target.getAttribute("data-action") === "save") { try { const ruleTargetParent = rule.target.parentNode; const ruleIndex = ruleTargetParent.parentNode.parentNode.getAttribute("data-index"); const typeArray = ruleTargetParent.getElementsByClassName("active"); if (typeArray.length !== 1) { throw new Error( "One and only one rule type should be selected. Please refresh the page and try again." ); } const type = typeArray[0].textContent; const expression = ruleTargetParent.querySelector('[data-input="expression"]').value; const message = ruleTargetParent.querySelector('[data-input="message"]').value; const textColor = ruleTargetParent.querySelector('[data-input="textColor"]').value; const backgroundColor = ruleTargetParent.querySelector('[data-input="backgroundColor"]').value; chrome.storage.sync.set({ [ruleIndex]: { type, expression, message, textColor, backgroundColor, }, }); const toggleButton = ruleTargetParent.parentNode.parentNode.querySelector('[data-toggle="collapse"]'); toggleButton.innerHTML = `${type} "${expression}" ↓`; displayAlert("success", "The rule was successfully saved!"); } catch (error) { console.log(error); displayAlert( "danger", "The rule could not be saved. Please refresh the page and try again." ); } } } function removeRule(rule) { if (rule.target.getAttribute("data-action") === "remove") { try { const ruleNode = rule.target.parentNode.parentNode.parentNode; chrome.storage.sync.remove(ruleNode.getAttribute("data-index")); ruleNode.remove(); displayAlert("success", "The rule was successfully removed!"); } catch (error) { console.log(error); displayAlert( "danger", "The rule could not be removed. Please refresh the page and try again." ); } } } 우리의 내용 스크립트 (content.js) 는 현재 진행 중인 실제 작업을 확장하는 것을 대표합니다.페이지를 탐색할 때마다 로컬 저장소에서 모든 규칙을 검색한 다음 탐색한 페이지의 URL이 규칙에 정의된 스키마와 일치하는지 확인합니다.만약 이렇다면, 단락 요소를 채우고, 시작 태그 뒤에 삽입합니다.
    내용.js chrome.storage.sync.get(null, function (items) { Object.values(items).forEach(function (item) { const ruleType = item.type; const url = window.location.href; const expression = item.expression; if ( (ruleType === "URL begins with" && urlBeginsWith(url, expression)) || (ruleType === "URL contains" && urlContains(url, expression)) || (ruleType === "URL ends with" && urlEndsWith(url, expression)) ) { document.body.prepend( createMessage( item.font, item.message, item.textColor, item.backgroundColor ) ); } }); }); function urlBeginsWith(url, expression) { const regex = new RegExp(expression + ".*"); return regex.test(url); } function urlContains(url, expression) { const regex = new RegExp(".*" + expression + ".*"); return regex.test(url); } function urlEndsWith(url, expression) { const regex = new RegExp(".*" + expression); return regex.test(url); } function createMessage(font, text, textColor, backgroundColor) { const paragraph = document.createElement("p"); paragraph.style.backgroundColor = backgroundColor; paragraph.style.color = textColor; paragraph.style.fontFamily = font; paragraph.style.textAlign = "center"; paragraph.style.padding = "1rem 0"; paragraph.style.fontFamily = "Arial,Helvetica,sans-serif"; paragraph.style.margin = "0 0 1rem 0"; paragraph.innerText = text; return paragraph; } 일부 요소를 분리해서 코드를 만들기 위해서, 우리는 별도의 helpers 파일 (helpers.js) 을 가지고 있습니다.옵션입니다.js 파일이 너무 커져서 더 이상 스캔하기가 쉽지 않습니다.이러한 지원 함수는 주로 옵션 페이지에 대한 DOM 요소를 만드는 데 초점을 맞춥니다.
    조수 1js function createTypeButtonGroup(value) { const typeButtonGroup = document.createElement("div"); typeButtonGroup.classList.add("btn-group", "btn-group-toggle", "mb-3"); typeButtonGroup.setAttribute("data-toggle", "buttons"); typeButtonGroup.setAttribute("data-purpose", "type"); // Create dropdown options based on RULE_TYPE_OPTIONS array for (i = 0; i < RULE_TYPE_OPTIONS.length; i++) { const typeOptionLabel = document.createElement("label"); typeOptionLabel.classList.add("btn", "btn-secondary"); typeOptionLabel.textContent = RULE_TYPE_OPTIONS[i]; const typeOptionInput = document.createElement("input"); typeOptionInput.setAttribute("type", "radio"); typeOptionInput.setAttribute("name", "options"); typeOptionInput.setAttribute("id", "option" + (i + 1)); if (value === RULE_TYPE_OPTIONS[i]) { typeOptionInput.checked = true; typeOptionLabel.classList.add("active"); } typeOptionLabel.appendChild(typeOptionInput); typeButtonGroup.appendChild(typeOptionLabel); } return typeButtonGroup; } function createExpressionInput(expression) { const inputGroup = document.createElement("div"); inputGroup.classList.add("input-group", "mb-3"); const inputGroupPrepend = document.createElement("div"); inputGroupPrepend.classList.add("input-group-prepend"); const inputGroupText = document.createElement("span"); inputGroupText.classList.add("input-group-text"); inputGroupText.innerText = "String:"; inputGroupPrepend.appendChild(inputGroupText); const input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("class", "form-control"); input.setAttribute("placeholder", "https://www.example.com"); input.setAttribute("aria-label", "URL"); input.setAttribute("minlength", "1"); input.setAttribute("maxlength", "255"); input.setAttribute("data-input", "expression"); if (expression) { input.value = expression; } inputGroup.appendChild(inputGroupPrepend); inputGroup.appendChild(input); return inputGroup; } function createMessageInput(message) { const inputGroup = document.createElement("div"); inputGroup.classList.add("input-group", "mb-3"); const inputGroupPrepend = document.createElement("div"); inputGroupPrepend.classList.add("input-group-prepend"); const inputGroupText = document.createElement("span"); inputGroupText.classList.add("input-group-text"); inputGroupText.innerText = "Message:"; inputGroupPrepend.appendChild(inputGroupText); const input = document.createElement("input"); input.setAttribute("type", "text"); input.setAttribute("class", "form-control"); input.setAttribute("placeholder", "Hi there!"); input.setAttribute("minlength", "1"); input.setAttribute("maxlength", "255"); input.setAttribute("aria-label", "Message"); input.setAttribute("data-input", "message"); if (message) { input.value = message; } inputGroup.appendChild(inputGroupPrepend); inputGroup.appendChild(input); return inputGroup; } function createColorInput(colorType, color) { const div = document.createElement("div"); div.classList.add("mb-3"); const label = document.createElement("label"); const input = document.createElement("input"); input.setAttribute("type", "color"); input.setAttribute("width", "50"); if (colorType === "textColor") { label.setAttribute("for", "textColor"); label.innerText = "Text color:"; input.setAttribute("data-input", "textColor"); input.setAttribute("aria-label", "Text color"); input.defaultValue = DEFAULT_TEXT_COLOR; } if (colorType === "backgroundColor") { label.setAttribute("for", "backgroundColor"); label.innerText = "Background color:"; input.setAttribute("data-input", "backgroundColor"); input.setAttribute("aria-label", "Background color"); input.defaultValue = DEFAULT_BACKGROUND_COLOR; } if (color) { input.value = color; } div.appendChild(label); div.appendChild(input); return div; } function createButton(type) { if (type === "save") { const saveButton = document.createElement("button"); saveButton.innerText = "Save"; saveButton.classList.add("btn", "btn-primary", "mb-3", "mt-3"); saveButton.setAttribute("data-action", "save"); return saveButton; } if (type === "remove") { const removeButton = document.createElement("button"); removeButton.innerText = "Remove"; removeButton.classList.add("btn", "btn-danger", "mb-3"); removeButton.setAttribute("data-action", "remove", "mt-3"); return removeButton; } } function displayAlert(type, text) { removeActiveAlert(); const newAlert = document.createElement("div"); newAlert.setAttribute("role", "alert"); newAlert.innerText = text; if (type === "success") { newAlert.classList.add("alert", "alert-success"); } if (type === "danger") { newAlert.classList.add("alert", "alert-danger"); } document.body.prepend(newAlert); setTimeout(function () { newAlert.remove(); }, 2000); } function removeActiveAlert() { const activeAlert = document.getElementsByClassName("alert"); if (activeAlert.length > 0) { activeAlert[0].remove(); } } function getCurrentNumberOfRules() { return parseInt(document.querySelectorAll(".rule").length, 10); } 마지막으로 가장 중요하지 않은 것은 앞으로 더 많은 패턴을 쉽게 확장하거나 기본값을 변경할 수 있도록 설정 파일 (config.js) 을 추가할 것입니다.
    배치하다.js const RULE_TYPE_OPTIONS = ["URL begins with", "URL contains", "URL ends with"]; const DEFAULT_TEXT_COLOR = "#ffffff"; const DEFAULT_BACKGROUND_COLOR = "#dc3545";
    기한을 연장하다
    이것이 바로 크롬 확장을 개발하는 데 필요한 모든 코드입니다.물론 이것은 그것이 채택할 수 있는 가장 간단한 형식이며 개선할 여지가 매우 크다.다음은 몇 가지 가능한 조정입니다.
  • 새 규칙을 추가할 때 새 규칙 모드와 일치하는 탭이 있는지 확인하고 단락을 삽입해야 합니다.페이지를 새로 고쳐야 합니다.
  • 글꼴 시리즈, 글꼴 크기, 이미지 추가 등 사용자 정의 옵션을 추가합니다.
  • 이 메시지는 현재 앞에 있습니다.DOM 구조에 따라 불필요한 결과가 발생할 수 있습니다.격차를 발견하기 위해서는 여러 개의 웹 사이트와 웹 응용 프로그램에서 더 많은 테스트를 해야 한다.
  • ...
  • 이거 좋아했으면 좋겠어.마음대로 평론하거나 질문하세요.

    좋은 웹페이지 즐겨찾기