origin

The company is developing a Chrome extension that simulates human manipulation to crawl data from targeted websites.

Through this article to record and share the experience and experience in the development process.

When this extension is developed, the Chrome extension V3 documentation has been released. However, it is only supported by the latest Chrome. For compatibility reasons, the extension still uses v2 version, and the relevant introduction in this article is based on V2 version.

Although the title is developing Chrome extensions using Umi, 95% of this article is about the extension itself, with a brief description of how Umi is configured when developing extensions.

Chrome extension

The official introduction

Extenders are small software programs that can customize the browsing experience. It allows users to customize Chrome features and behavior based on their personal needs or preferences. They are built based on Web technologies such as HTML, JavaScript, and CSS. Extensions consist of interrelated components that can include background scripts, content scripts, option pages, UI elements, and various logical files.

Overview of common extension components and functionality

This section introduces common extension components and functions to show you what extensions can do in the browser.

BrowserAction, pageAction

Small ICONS displayed in the upper right corner of the browser (toolbar), each icon represents an extension.

Action has three modes of operation:

  • toopli: the mousehoverAfter the text prompt
  • badge: logo
  • popup: Click the back popup

The popup window is the main interaction area of the action.

Commands (keyboard shortcuts)

As the name suggests, you can add and modify browser keyboard shortcuts.

Speaking of this feature, we have to mention a very popular extension: Vimium

This extension lets you use vim shortcuts to navigate your browser, taking you completely off the mouse.

contextMenu

Adds a custom item to the right-click menu.

override

You can use Override to replace some of Chrome’s default pages with pages provided by the extension.

Alternative pages are as follows:

  • Chrome://historyHistory page
  • Chrome://newtabA new TAB
  • Chrome://bookmarksbookmarks

An extension can only replace one page, not the new TAB page of the traceless mode window.

New TAB page extensions:The Denver nuggets,Infinity

OmniBox

Register a keyword in the address bar. The user enters the specified keyword and presses TAB to enter the content. Each time the user presses Enter, the content entered in the address bar will be sent to the extension.

Baidu, Bing, Github and other websites have corresponding keyword search functions in Chrome, I believe many people have used. Enter github.com or Baidu.com in the address bar, then press TAB, and the address bar will look like this. In this state, everything we enter will be searched and executed on the corresponding website.

Devtools (Developer tools)

Add features to developer tools, such as Vue devTools and React DevTools, and you’ll be familiar with the front end.

Option (Options page)

Right-click on the Action icon and the options menu appears in the menu. If the menu is lit, this extension has options enabled.

The options page is generally used as the extended configuration page, as shown below

Of course, the options page simply loads a specified HTML, and it’s up to you whether the HTML shows the extension’s configuration or something else.

These are all functions that the user can feel directly. The next two script functions are functions that the user can’t feel directly, but are extremely important in the extension.

Background-script

Background script refers to the JS file that runs with the entire life cycle of the extension. In this JS, you can use the API provided by Chrome to monitor various events of the browser and extension. By monitoring these events, you can coordinate and process other functions of the extension.

Content-script

Content-script can place the specified JS and CSS files in the context of the web page being viewed.

CSS can modify the style of the web page, JS can access and change the BOM and DOM of the current page, and then control and change the style and behavior of the web page.


Chrome also provides a number of powerful apis. Let’s list some of the most common ones.

  • Management: Manages installed and running extensions

    • Extension management
  • Message Passing: Communication between extensions and between extensions and Content-Script

  • Storage: provides the local Storage and account synchronization function

  • Tabs: Creates, modifies, and rearranges Tabs in the browser

    • oneTab
  • Windows: Create, modify, and rearrange Windows in the browser

  • Cookies: The cookie system used to browse and modify the browser

    • cookies
  • Cross-origin: XMLHttpRequest and fetchAPI in the extension are not affected by the same Origin policy.

  • WebRequest: Intercepts, blocks, or modifies requested network requests

  • Bookmarks: Bookmarks create, organize, and manipulate Bookmarks

  • Downloads: Programmatically start, monitor, manipulate, and search for Downloads

  • History: Records the interaction between pages accessed by the browser

  • Devtools: Add functionality to developer tools

  • Accessibility(a11y) : Accessibility

  • Internationalization(i18n) : Internationalization

  • Identity: OAuth2 Access token

  • Proxy: Manages Chrome Proxy Settings

    • VPN

How components are used

Before we start, we need to know one more thing: Mainfest.json.

Mainfest

In the last section, we talked about many of the features of extensions. Going back to the beginning, the official introduction states that these components are built with Web technology, which means that all we need to do is provide Chrome with HTML, CSS, and JS, and Chrome can run them as extensions.

This raises the question, how does Chrome know that these are the files the extension is supposed to run on? How does it know which files correspond to which function?

So you need a configuration file that tells Chrome how the extension should be built and how it should work. That’s where Mainfest.json comes in.

The detailed mainfest.json configuration will come later, but we’ll move on after we’ve seen the concept here.

background-script

Start with background scripts, because some extensions depend on it to run.

Configuration mode:

// manifest.json
{
  "background": {
    // Choose one of two ways
    "page": "background.html"."scripts": ["background.js"].// Close persistent connections
     "persistent": false}}Copy the code

In the manifest.json background property, you can specify either a JS array, or an HTML.

HTML simply loads the JS that executes it, and the content of HTML itself is not displayed.

The persistent attribute represents how background scripts are run, which by default is true, meaning they are always running. If specified as false, it will only run in critical events.

  • The extension is first installed or updated to a new version.
  • The background page is listening for an event and has scheduled the event.
  • Content scripts or other extensions send messages.
  • Another view (such as a pop-up window) called in the extensionruntime.getBackgroundPage.

The official recommendation is to set it to false, and in V3, the persistent attribute is removed in favor of specifying background scripts using Service Script, which will run as a service worker.

Again, how persistent is set depends on the functionality of the extension. If you don’t know how to configure this, specify false, because most background scripts should be event-driven.

Background scripts have access to all of Chrome’s apis except devTools.

content-script

Configuration mode:

{
	"content_scripts": [
		{
			"matches": ["<all_urls>"],
			"js": ["js/content-script.js"],
			"css": ["css/custom.css"],
			"run_at": "document_start",
      // "exclude_matches": "",
      // "include_globs": ""
      // "exclude_globs": ""
      // "match_about_blank": false
		},
    {
			"matches": ["*://*.baidu.com/*"],
			"js": ["bd-content.js"],
			"run_at": "document_start"
		}
	]
}
Copy the code

The content_scripts property is an array in which multiple matching rules can be configured, and when a match is successful, the configured file will be injected for execution.

  • matches: specifyMatch the pattern, “<all_urls>” represents all urls.
  • js/css: The file to inject.
  • match_about_blank: Whether the script should be injected intoabout:blankPage, defaultfalse.
  • exclude_matches/include_globs/exclude_globs: Configures additional matching modes.
  • run_at: Timing of code injection
    • document_start: is injected after CSS injection, before building DOM and running script files.
    • document_end: After DOM loading is complete, it can be understood asDOMContentLoadedEvents.
    • document_idle: Default value, inwindow.onloadBefore and after the execution of the event invocation. The timing depends on the complexity of the document and how long it takes to load, and is optimized for page loading speed. No listening is required in this injection modeonloadEvent to ensure that the DOM has been loaded. If you must knowonloadThere is no trigger, can be useddocument.readyStateJudge.

For security reasons, content-script JS is executed in a sandbox environment. It does not access the properties and methods defined by JS loaded in the web page itself. For example, jquery is loaded in the web page itself. This can only be configured in configuration items, injecting a jquery into the content-Script execution environment.

Content-script can modify, delete, and add to the DOM of a page at will. It can bind events to existing DOM elements, or create a new DOM and add events to it and insert them into the page. Normally, this is enough to meet most of your needs.

Content-script can’t bind events to the DOM. After testing, it works. I don’t know if the extension has been updated.

If there is a real need, need JS to execute in the current web page execution environment, that can be implemented, since we can manipulate DOM, we can easily write the following code:

const script = document.createElement('script')
script.innerHTML = 'console.log(window.$)'
document.body.append(script)
Copy the code

The JAVASCRIPT injected by manipulating the DOM is then executed in the web page’s own execution environment.

Of course, you wouldn’t normally do this with innerHTML, but instead load a JS file directly using the SRC attribute of the script. The code is as follows:

	const script = document.createElement('script')
	script.src = chrome.extension.getURL('js/inject.js')
	document.head.appendChild(script)
Copy the code

Need to be aware of is the path of the load is extensions directory file (of course also can load network resources), the directory address. We need through the chrome extension, getURLAPI to gain.

Also note that if you are loading files that extend directories, you need to specify the configuration file name under web_accessibLE_resources (not for web resources).

{
  "web_accessible_resources": [
    "inject.js"
  ]
}
Copy the code

Content scripts can only access the following Chrome APIS

  • i18n
  • storage
  • extension
  • runtime
    • connect
    • onConnect
    • onMessage
    • sendMessage
    • getManifest
    • getURL
    • id

The first four apis in the Runtime provide the ability to communicate with the rest of the extension.

BrowserAction, pageAction

In the previous section, when an action is clicked, a window pops up. This window is basically a small TAB page that loads the HTML file we specified.

Configuration mode:

// manifest.json
{
  // "page_action"
  "browser_action": {
    / / icon
    "default_icon": "img/icon.png".// tooltip
    "default_title": "Title".// Popup page
    "default_popup": "popup.html"}}Copy the code

You can see that in the default_popup field we specify an HTML that will be loaded when the popover opens and destroyed when the popover closes.

In other words, every popover’s appearance and disappearance is a complete life cycle. It’s like opening a TAB page in your browser to load a page, and then closing the TAB.

BrowserAction vs. pageAction
  • browserActionThe icon is always on, and its function can be used in any web page.
  • pageActionIt only lights up in designated websites, and its functionality is limited to those designated websites.
    • octotreeThe extension will only work ingithubThe project page lights up.

DeclarativeContent is used in background.js to match the changes to the page and then to determine the lighting and grashing of the pageAction.

// manifest.json
{
  "permissions": ["declarativeContent"]
}

// background.js
chrome.runtime.onInstalled.addListener(function(details) {
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [
        new chrome.declarativeContent.PageStateMatcher({
          pageUrl: { hostEquals: 'www.google.com', schemes: ['https'] },
          css: ["input[type='password']"]
        })
      ],
      actions: [ new chrome.declarativeContent.ShowPageAction() ]
    }]);
  });
});
Copy the code

In Chrome Ext V3, browserAction and pageAction, which are not very different from each other, are merged into the action function.

Action, like Background, has access to all of Chrome’s apis except DevTools.

In the action by chrome. The extension. GetBackgroundPage or chrome. The runtime. GetBackgroundPage direct access to the background script window object, To access the methods and properties.

The difference between these two apis background of persistent property, if the value is false, the spare time background script is closed, you need to use the runtime. GetBackgroundPage event mechanism through its awakening, and then to interaction.

const bgs = chrome.extension.getBackgroundPage()
// or
chrome.runtime.getBackgroundPage((bgs) => {
  bgs.backgroundFunction()
})
Copy the code

contextMenu

Declare in the permission configuration that we want the contextMenus permission, and then make an icon for it.

{
 "permissions": [
    "contextMenus"
  ],
  "icons": {
    "16": "icon-bitty.png",
    "48": "icon-small.png",
    "128": "icon-large.png"
  }
}
Copy the code

Using the Chrome. ContextMenus API, you can add, delete, change, and check menu items in background-js.

Chrome. ContextMenus. Create ({type: 'normal', / / type, optional: [" normal ", "checkbox", "radio", "the separator"], the default normal title: 'Name of menu ', // Display text. This parameter is required unless it is of the "separator" type. If it is of the "selection" type, you can use %s to display selected text contexts: ['page'], // Context, optional: ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"], default page onclick: Function (){}, // parentId: 1, // right-click the menu item's parent menu item ID. Specifying a parent menu item makes it a child of the parent menu item documentUrlPatterns: 'https://*.baidu.com/*' // Displays this right-click menu only on certain pages}); / / remove a menu item chrome contextMenus. Remove (menuItemId); / / delete all custom right-click menu. Chrome contextMenus. RemoveAll (); / / update chrome a menu item. The contextMenus. Update (menuItemId updateProperties);Copy the code

override

// Override "newtab": "newtab.html", "history": "history.html", "bookmarks": "bookmarks.html"}Copy the code

With the extended apis outlined in the previous section, we can take the data we need and render it.

Devtools (Developer tools)

This function does not go into depth, here is a copy of the official website.

Each time the Devtools window is opened, an instance of the extended Devtools page is created. The DevTools page exists throughout the life of the DevTools window. The DevTools page has access to the DevTools API and a limited set of extension apis. Specifically, DevTools pages can:

  • usedevtools.panelsThe API creates and interacts with panels.
  • Get information about the check window and usedevtools.inspectedWindowThe API evaluates the code in the inspection window.
  • usedevtools.networkThe API retrieves information about network requests.

DevTools pages are similar to Content-Script and have limited access to the Chrome API. DevTools pages communicate with background pages using runtime apis as well.

{// Point to only one HTML file, not JS file "devtools_page": "devtools.html"}Copy the code

Devtools doesn’t have a lot of development scenarios, so see the resources at the end of this article for more information.

omnibox

First specify a keyword in manifest.json to provide search suggestions (only one keyword can be set)

{
	"omnibox": { "keyword" : "go" },
}
Copy the code

Listen for related events in background-js

/ / input box content changes triggered, suggest to prompt input advice chrome. The omnibox. OnInputChanged. AddListener ((text, suggest) = > {the if (! text) return; If (text == '女') {suggest([{content: '中国' + text, description: '" Chinese beauty "? You are looking for'})}}) / / when the user receives the keyword suggestion triggered when chrome. The omnibox. OnInputEntered. AddListener ((text) = > {});Copy the code

Option (Options page)

Specify render HTML in manifest.json

{
  "options_ui": {
    "page": "options.html",
    "chrome_style": true
  }
}
Copy the code

manifest.json

Official manifest format

{
  // The version of the manifest file, fixed at 2, is now 3
  "manifest_version": 2.// The name of the plug-in
  "name": "demo".// Version of the plug-in
  "version": "1.0.0".// Plug-in description
  "description": "Simple Chrome extension Demo".// Use the same size icon
  "icons": {
    "16": "img/icon.png"."48": "img/icon.png"."128": "img/icon.png"
  },
  
  // The background JS or background page will always be resident
  "background": {
    // If you specify JS, a background page will be generated automatically
    "page": "background.html"
    //"scripts": ["js/background.js"]
  },
  
  // Set the icon in the upper right corner of the browser. Browser_action, Page_Action or APP must be selected
  "browser_action": {
    "default_icon": "img/icon.png".// The title of the hovering icon, optional
    "default_title": "This is a sample Chrome plugin"."default_popup": "popup.html"
  },
  // An icon that is displayed only when certain pages are opened
  "page_action":
	{
		"default_icon": "img/icon.png"."default_title": "I am pageAction"."default_popup": "popup.html"
	},
  
  // Need to inject the JS directly into the page
  "content_scripts": [{//"matches": ["http://*/*", "https://*/*"],
      // "
      
       " matches all addresses
      
      "matches": ["<all_urls>"].// Multiple JS are injected in sequence
      "js": ["Js/jquery - 1.8.3. Js"."js/content-script.js"].// JS injection can be arbitrary, but CSS must be careful, because careless can affect the global style
      "css": ["css/custom.css"].Optional values: "document_start", "document_end", or "document_idle". The last one indicates that the page is idle, and the default is document_idle
      "run_at": "document_start"
    },
    // Just to demonstrate that Content-script can be configured with multiple rules
    {
      "matches": ["*://*/*.png"."*://*/*.jpg"."*://*/*.gif"."*://*/*.bmp"]."js": ["js/show-image-content-size.js"]}],// Permission request
  "permissions": [
    "contextMenus".// Right-click the menu
    "tabs"./ / label
    "notifications"./ / notice
    "webRequest"./ / web request
    "webRequestBlocking"."storage".// Plugin local storage
    "http://*/*".// A website accessible from executeScript or insertCSS
    "https://*/*" // A website accessible from executeScript or insertCSS].// A list of plug-in resources that can be accessed directly from a normal page
  "web_accessible_resources": ["js/inject.js"].// Plugin home page, this is very important, do not waste this free advertising space
  "homepage_url": "https://www.baidu.com".// Override the browser default page
  "chrome_url_overrides": {
    // Overrides the browser's default new TAB page
    "newtab": "newtab.html"
  },
  // plugin configuration page before Chrome40
  "options_page": "options.html".// Chrome40 after the plugin configuration page, if both write, the new version of Chrome only recognize the latter one
  "options_ui": {
    "page": "options.html".// Add some default styles, recommended
    "chrome_style": true
  },
  // Register a keyword to the address bar to provide search suggestions. Only one keyword can be set
  "omnibox": { "keyword": "go" },
  // Default language
  "default_locale": "zh_CN".// DevTools page entry, note that can only point to an HTML file, not JS file
  "devtools_page": "devtools.html"
}
Copy the code

Message communication

The communication between Content-Script and popup and background is mainly used during development, and the communication is divided into short links and long links.

The communication between the two processes must be serialized and understood to be delivered by json.stringify. Function, symbol, Map, etc., cannot be sent in the message body.

popup <—> background

In the background can be achieved by chrome. The extension. GetViews ({type: ‘popup’}) to get open popup, and access to the properties and methods.

Popup by chrome. The extension. GetBackgroundPage or chrome. Runtime. GetBackgroundPage access to the background of the window, and then access the properties and methods.

popup | background ==> content-script

Popup and background send messages to Content-script

Short link

The receiver Content-script needs to finish listening for message events first

const handleMessage = (message, sender, sendResponse) => {  }
chrome.runtime.onMessage.addListener(handleMessage)
Copy the code
  • message: Message content
  • sender: Indicates the sender information
  • sendResponse: Method of replying to a message

Call API sends the message sender popup | background

Const getCurrentTab = () => new Promise((resolve, reject) => {chrome.tabs. Query ({active: true, currentWindow: true}, ([tab]) => { tab? .id ? resolve(tab) : Reject ('not found active TAB ')})}) const TAB = await getCurrentTab() chrome.tabs. SendMessage (tab.id, {greeting: "hello"}, (response) => { })Copy the code

The three parameters of tabs. SendMessage are

  • To communicate withtabThe TABid
  • The message body
  • The response callback function: this is the one abovesendResponsefunction

Notes for short links:

  • sendResponseYou can only use it once, not multiple times.
  • By default,handleMessageThe message channel is closed at the end of the functionsendResponseNo longer valid. That is to say,sendResponseCannot be used asynchronously.
  • If asynchronous use is requiredsendResponseThat need to be inhandleMessageWrite down explicitlyreturn true, so that the message channel will remain untilsendResponseIs invoked.

Long links

The receiver Content-script needs to finish listening for message events first

/ / listen long link link event chrome runtime. OnConnect. AddListener (port = > {/ / may, according to the name to distinguish between different long logical link if (port. The name = = = 'knockknock') { / / messages to the other end port. PostMessage () / / listening at the other end of the message port. OnMessage. AddListener (message = > {})}})Copy the code

Call API sends the message sender popup | background

Const TAB = await getCurrentTab() const port = chrome.tabs. Connect (tab.id, {name: "Knockknock"}) / / messages to the other end port postMessage () / / listening at the other end of the message port. The onMessage. AddListener (message = > {})Copy the code
  • portPort object
    • name: Port name
    • disconnect: Disable the port
    • postMessage: Sending a message
    • onDisconnect: Listens for port closure events
    • onMessage: Listens for port message events
    • sender: Indicates the sender’s information

content-script —> popup | background

Content-script sends messages to popup and background

The logic is the same, only the popup | background to the content – the script sends the message, use chrome. Tabs API, you need to specify a TAB id.

And the content – the script to popup | background send message, use the chrome. When the runtime API, don’t need id.

Short link

The receiver popup | background need to finish listening news events

const handleMessage = (message, sender, sendResponse) => {  }
chrome.runtime.onMessage.addListener(handleMessage)
Copy the code

The sender content-script calls the API to send the message

/ / change tabs to the runtime chrome. Runtime. SendMessage ({the greeting: "hello"}, (response) = > {})Copy the code

If both popup and background listen for events using Runtime. onMessage, then when the Content-Script sends a message, both receive it. However, there is only one sendResponse, and the latter cannot be used if one is used first.

There’s also a pit, which we’ll talk about in the next video.

Long links

The receiver popup | background need to finish listening news events

/ / and the chrome. The same runtime. The onConnect. AddListener (port = > {the if (port. The name = = = 'knockknock') {port. PostMessage () port.onMessage.addListener(message => {}) } })Copy the code

The sender content-script calls the API to send the message

// Tabs changed to Runtime const port = chrome.runtime.connect({name: tabs) "knockknock"}) port.postMessage() port.onMessage.addListener(message => {})Copy the code

It can be seen that there is almost no difference between the two except for API calls. Who actively sends messages and who listens depends on actual requirements.

Reply CS short link message stomp pit

Said in the short link, popup | background reply the content – the pit when the script.

Preconditions for the question:

  • sendResponseNeed to send asynchronously.
  • popupandbackgroundAll useruntime.onMessageListen to thecontent-scriptMessage sent.

When you encounter the above scenario, you find that you cannot reply to a message after calling sendResponse.

The reason for this is simple, as we have already mentioned above. When a sendResponse needs to be sent asynchronously, it needs to explicitly return true in the Runtime. onMessage listener, but since both are listening, one of them may return undefined beforehand. This leads to an early closure of the message channel.

The solution is simple, we need to separate and encapsulate the messages sent to popup and background, and send whether the messages are asynchronous or not to the recipient.

The following code

// Message format interface RuntimeMessage<T = string> {type: T payload: any receiver? : 'bgs' | 'popup' isAsync? Const sendMessageToRuntime = (MSG: RuntimeMessage, cb? : LooseFunction,) => {// When called callback, This is an asynchronous message by default the if (MSG. IsAsync = = = void 0 && isFunction (cb)) {MSG. IsAsync = true} chrome. Runtime. SendMessage (MSG, Cb)} // sendMessageToRuntime({type: 'crossFetch', payload: {... }, receiver: 'bgs', }, (response) => {}) // background.js chrome.runtime.onMessage.addListener( ( { type, payload, receiver, isAsync, }, sender, sendResponse, ) => { if (receiver === 'bgs') {... } return isAsync }, ) // popup.js chrome.runtime.onMessage.addListener( ( { type, payload, receiver, isAsync, }, sender, sendResponse, ) => { if (receiver === 'popup') {... } return isAsync }, )Copy the code

Communication between two tabs

Due to this requirement during the development process, regular JS methods cannot establish communication with another TAB page (the new TAB page address is the result of multiple redirects).

Background is used as a message channel to establish communication between two tabs.

// TAB page 1 Content-script const port = chrome.runtime.connect({name: PostMessage ({type: 'createTab', // TAB information payload: {... }})/port/listen to news. The onMessage.. addListener handleMessage () / / port message. The postMessage ({type: "message", content: {... }})Copy the code
/ / the content TAB page 2 - script. Chrome runtime. OnConnect. AddListener (port = > {if (port. The name = = = 'createTabAndConnect') {/ / Listening to the news port. The onMessage. AddListener (handleMessage) / / messaging port. PostMessage ({type: "message", content: {... }})}})Copy the code
// background.js port.onMessage.addListener(async ({ type, payload }) => { switch (type) { case 'createTab': {// create TAB const TAB = await new Promise(resolve => {chrome.tabs. Create (payload, Resolve)}) / / to monitor the state of the TAB page. Chrome tabs. OnUpdated. AddListener ((id, Info) => {if (id == tab.id) {// Load complete if (info.status === 'complete') {// Create a link tabPort = Chrom.tabs. Connect (id, {name: the port name,}) / / listening news tabPort onMessage. AddListener (MSG = > {/ / transit tabPort message to the port of the port. PostMessage ({type: 'message', payload: msg, }) }) } } }) } return case 'message': TabPort?. PostMessage (payload) return}})Copy the code

Initialize the project using Umi

Finally, a simple Umi development extension configuration.

The idea is to add the script file to the entry file and package it separately. The HTML page is routed and accessed through hash.

So pages that need views, such as popup and Options, can be written directly in the Pages folder. Umi packages them as routes by default.

// manifest.json
{
	"browser_action": {
		"default_popup": "index.html#/popup"
	},
  "options_ui": {
    "page": "index.html#/options",
  }
}
Copy the code

Write content-Script and background files in the scripts folder and add entry files to Umi’s configuration file.

The final.umirc.ts looks like this:

// .umirc.ts
import { defineConfig } from 'Umi'

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',},cssLoader: {
    localsConvention: 'camelCase',},dynamicImport: {},
  history: {
    type: 'hash',},targets: {
    Chrome: 73.firefox: false.safari: false.edge: false.ios: false,},ignoreMomentLocale: true.devServer: {
    writeToDisk: true,},copy: ['manifest.json'.'index.html'.'hot-reload.js'] as any.chainWebpack(memo, { env }) {
    memo.devServer.hot = false as any
    memo.plugins.delete('hmr')
    memo
      .entry('background')
        .add('./src/scripts/background.ts')
        .end()
      .entry('content-script')
        .add('./src/scripts/content-script.ts')
        .end()
  },
})
Copy the code
  • The Targets configuration determines how your code is polyfilled, and obviously as a Chrome plugin we don’t need polyfill from other browsers.

  • DevServer writes the development mode files in memory to disk, and WebPack turns off hot updates.

    • Extension development is not like normal page development, so hot updates don’t work here.
    • Files in memory are written to disk before the browser loads them.
  • Copy manifest.json to the root directory by copy.

  • Copy index. HTML because Umi injects two scripts into index. HTML by default, and extended HTML does not allow inline scripts. If there are, there will be two errors, which of course have no effect other than ugly. So it doesn’t matter if I don’t do it here.

  • Normally, after editing the file, you need to refresh the extension and the current TAB page to see the effect. Hot-reload. js can help us to automatically refresh, detailed usage can be click in view.

Recommended reading

  • Official component demo

The resources

  • Official Documents (Self-provided Ladders)
  • [dry goods] Chrome plug-in (extension) development overview
  • Umi Official Documents