Build extension with esbuild (#1476)

* window.bundle.js seems to work

* minify

* Use API instead of CLI

* Add remote bundle

* Perform code-splitting

* Add background and stop code-splitting

* Add other entrypoints

* Flesh out some more

* Keep going

* Copy to browser directories

* Ignore import type error

* Strip out webpack stuff

* Remove todos

* Remove pug imports

* Fix
This commit is contained in:
Nathan Bierema 2023-08-30 20:59:38 -04:00 committed by GitHub
parent 546c98d406
commit 4e0620c131
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 532 additions and 626 deletions

71
extension/build.mjs Normal file
View File

@ -0,0 +1,71 @@
import * as fs from 'node:fs';
import * as esbuild from 'esbuild';
import pug from 'pug';
const args = process.argv.slice(2);
const prod = !args.includes('--dev');
const commonEsbuildOptions = {
bundle: true,
logLevel: 'info',
outdir: 'dist',
minify: prod,
sourcemap: !prod,
define: {
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
},
};
await esbuild.build({
...commonEsbuildOptions,
entryPoints: [
{ out: 'background.bundle', in: 'src/background/index.ts' },
{ out: 'options.bundle', in: 'src/options/index.tsx' },
{ out: 'window.bundle', in: 'src/window/index.tsx' },
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
...(prod ? [] : [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }]),
],
loader: {
'.woff2': 'file',
},
});
if (prod) {
await esbuild.build({
...commonEsbuildOptions,
entryPoints: [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }],
loader: {
'.js': 'text',
},
});
}
console.log();
console.log('Creating HTML files...');
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote', 'window'];
for (const htmlFile of htmlFiles) {
fs.writeFileSync(
`dist/${htmlFile}.html`,
pug.renderFile(`src/${htmlFile}/${htmlFile}.pug`),
);
}
console.log('Copying manifest.json...');
fs.copyFileSync('chrome/manifest.json', 'dist/manifest.json');
console.log('Copying assets...');
fs.cpSync('src/assets', 'dist', { recursive: true });
console.log('Copying dist for each browser...');
fs.cpSync('dist', 'chrome/dist', { recursive: true });
fs.copyFileSync('chrome/manifest.json', 'chrome/dist/manifest.json');
fs.cpSync('dist', 'edge/dist', { recursive: true });
fs.copyFileSync('edge/manifest.json', 'edge/dist/manifest.json');
fs.cpSync('dist', 'firefox/dist', { recursive: true });
fs.copyFileSync('firefox/manifest.json', 'firefox/dist/manifest.json');

View File

@ -11,12 +11,8 @@
"url": "https://github.com/reduxjs/redux-devtools.git" "url": "https://github.com/reduxjs/redux-devtools.git"
}, },
"scripts": { "scripts": {
"start": "webpack --env development --watch", "build": "pnpm run build:extension && pnpm run type-check",
"build": "pnpm run build:extension && pnpm run build:chrome && pnpm run build:edge && pnpm run build:firefox", "build:extension": "node build.mjs",
"build:extension": "webpack --env production && webpack --config wrap.webpack.config.js",
"build:chrome": "cpy . ../../chrome/dist --cwd dist && cpy manifest.json ../dist --cwd chrome",
"build:edge": "cpy . ../../edge/dist --cwd dist && cpy manifest.json ../dist --cwd edge",
"build:firefox": "cpy . ../../firefox/dist --cwd dist && cpy manifest.json ../dist --cwd firefox",
"build:examples": "babel-node examples/buildAll.js", "build:examples": "babel-node examples/buildAll.js",
"clean": "rimraf dist && rimraf chrome/dist && rimraf edge/dist && rimraf firefox/dist", "clean": "rimraf dist && rimraf chrome/dist && rimraf edge/dist && rimraf firefox/dist",
"test:app": "cross-env BABEL_ENV=test jest test/app", "test:app": "cross-env BABEL_ENV=test jest test/app",
@ -62,32 +58,23 @@
"@types/react": "^18.2.21", "@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"babel-loader": "^9.1.3",
"chromedriver": "^116.0.0", "chromedriver": "^116.0.0",
"copy-webpack-plugin": "^11.0.0",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.8.1",
"electron": "^26.1.0", "electron": "^26.1.0",
"esbuild": "^0.19.2",
"eslint": "^8.48.0", "eslint": "^8.48.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.28.1", "eslint-plugin-import": "^2.28.1",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
"immutable": "^4.3.4", "immutable": "^4.3.4",
"jest": "^29.6.4", "jest": "^29.6.4",
"jest-environment-jsdom": "^29.6.4", "jest-environment-jsdom": "^29.6.4",
"pug-html-loader": "^1.1.5", "pug": "^3.0.2",
"raw-loader": "^4.0.2",
"react-transform-catch-errors": "^1.0.2",
"react-transform-hmr": "^1.0.4",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"selenium-webdriver": "^4.11.1", "selenium-webdriver": "^4.11.1",
"sinon-chrome": "^3.0.1", "sinon-chrome": "^3.0.1",
"style-loader": "^3.3.3",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"typescript": "~5.1.6", "typescript": "~5.1.6",
"webpack": "^5.88.2", "webpack": "^5.88.2",

View File

@ -1,3 +1,4 @@
import '../chromeApiMock';
import { Store } from 'redux'; import { Store } from 'redux';
import configureStore, { BackgroundAction } from './store/backgroundStore'; import configureStore, { BackgroundAction } from './store/backgroundStore';
import openDevToolsWindow, { DevToolsPosition } from './openWindow'; import openDevToolsWindow, { DevToolsPosition } from './openWindow';

View File

@ -1,3 +1,4 @@
import '../chromeApiMock';
import { import {
injectOptions, injectOptions,
getOptionsFromBg, getOptionsFromBg,

View File

@ -1,3 +1,4 @@
import '../chromeApiMock';
import React, { CSSProperties, ReactNode } from 'react'; import React, { CSSProperties, ReactNode } from 'react';
import { createRoot, Root } from 'react-dom/client'; import { createRoot, Root } from 'react-dom/client';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
@ -6,7 +7,6 @@ import { REMOVE_INSTANCE, StoreAction } from '@redux-devtools/app';
import App from '../app/App'; import App from '../app/App';
import configureStore from './store/panelStore'; import configureStore from './store/panelStore';
import './devpanel.pug';
import { Action, Store } from 'redux'; import { Action, Store } from 'redux';
import type { PanelMessage } from '../background/store/apiMiddleware'; import type { PanelMessage } from '../background/store/apiMiddleware';
import type { StoreStateWithoutSocket } from './store/panelReducer'; import type { StoreStateWithoutSocket } from './store/panelReducer';

View File

@ -1,5 +1,3 @@
import './devtools.pug';
function createPanel(url: string) { function createPanel(url: string) {
chrome.devtools.panels.create( chrome.devtools.panels.create(
'Redux', 'Redux',

View File

@ -1,10 +1,9 @@
import '../chromeApiMock';
import React from 'react'; import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import OptionsComponent from './Options'; import OptionsComponent from './Options';
import { Options } from './syncOptions'; import { Options } from './syncOptions';
import './options.pug';
chrome.runtime.getBackgroundPage((background) => { chrome.runtime.getBackgroundPage((background) => {
const syncOptions = background!.syncOptions; const syncOptions = background!.syncOptions;

View File

@ -1,8 +1,10 @@
// @ts-ignore
import script from '../dist/page.bundle.js';
let s = document.createElement('script'); let s = document.createElement('script');
s.type = 'text/javascript'; s.type = 'text/javascript';
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
const { default: script } = require('raw-loader!../dist/page.bundle.js');
s.appendChild(document.createTextNode(script)); s.appendChild(document.createTextNode(script));
(document.head || document.documentElement).appendChild(s); (document.head || document.documentElement).appendChild(s);
s.parentNode!.removeChild(s); s.parentNode!.removeChild(s);

View File

@ -2,8 +2,6 @@ import React from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { Root } from '@redux-devtools/app'; import { Root } from '@redux-devtools/app';
import './remote.pug';
chrome.storage.local.get( chrome.storage.local.get(
{ {
'select-monitor': 'InspectorMonitor', 'select-monitor': 'InspectorMonitor',

View File

@ -7,8 +7,6 @@ import App from '../app/App';
import configureStore from './store/windowStore'; import configureStore from './store/windowStore';
import type { MonitorMessage } from '../background/store/apiMiddleware'; import type { MonitorMessage } from '../background/store/apiMiddleware';
import './window.pug';
const position = location.hash; const position = location.hash;
chrome.runtime.getBackgroundPage((window) => { chrome.runtime.getBackgroundPage((window) => {

View File

@ -1,81 +0,0 @@
const path = require('path');
const webpack = require('webpack');
const CopyPlugin = require('copy-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = function (env) {
return {
mode: env.production ? 'production' : 'development',
devtool: env.production ? undefined : 'eval-source-map',
entry: {
background: [
path.resolve(__dirname, 'src/chromeApiMock'),
path.resolve(__dirname, 'src/background/index'),
],
options: [
path.resolve(__dirname, 'src/chromeApiMock'),
path.resolve(__dirname, 'src/options/index'),
],
window: [path.resolve(__dirname, 'src/window/index')],
remote: [path.resolve(__dirname, 'src/remote/index')],
devpanel: [
path.resolve(__dirname, 'src/chromeApiMock'),
path.resolve(__dirname, 'src/devpanel/index'),
],
devtools: path.resolve(__dirname, 'src/devtools/index'),
content: [
path.resolve(__dirname, 'src/chromeApiMock'),
path.resolve(__dirname, 'src/contentScript/index'),
],
page: path.join(__dirname, 'src/pageScript'),
...(env.production
? {}
: { pagewrap: path.resolve(__dirname, 'src/pageScriptWrap') }),
},
output: {
filename: '[name].bundle.js',
},
plugins: [
new webpack.DefinePlugin({
'process.env.BABEL_ENV': JSON.stringify(process.env.NODE_ENV),
}),
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: 'tsconfig.json',
},
}),
new CopyPlugin({
patterns: [
{
from: path.join(__dirname, 'chrome/manifest.json'),
to: path.join(__dirname, 'dist/manifest.json'),
},
{
from: path.join(__dirname, 'src/assets'),
to: path.join(__dirname, 'dist'),
},
],
}),
],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.(js|ts)x?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css?$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.pug$/,
use: ['file-loader?name=[name].html', 'pug-html-loader'],
},
],
},
};
};

View File

@ -1,31 +0,0 @@
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
pagewrap: path.resolve(__dirname, 'src/pageScriptWrap'),
},
output: {
filename: '[name].bundle.js',
},
plugins: [
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: 'tsconfig.json',
},
}),
],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.(js|ts)x?$/,
use: 'babel-loader',
exclude: /(node_modules|dist\/page\.bundle)/,
},
],
},
};

File diff suppressed because it is too large Load Diff