Replace webpack with esbuild

This commit is contained in:
hensm
2021-05-26 18:32:46 +01:00
parent 13cd1127c6
commit d343ac4629
16 changed files with 10690 additions and 393 deletions

View File

@@ -16,7 +16,8 @@ const paths = require("./lib/paths");
const { author
, homepage } = require("../../package.json");
const { __extensionId: extensionId } = require("../../ext/package.json");
const EXTENSION_ID = "fx_cast@matt.tf";
// Command line args
@@ -98,7 +99,7 @@ async function build () {
"name": meta.__applicationName
, "description": ""
, "type": "stdio"
, "allowed_extensions": [ extensionId ]
, "allowed_extensions": [ EXTENSION_ID ]
};
/**

View File

@@ -1,29 +1,26 @@
"use strict";
const esbuild = require("esbuild");
const fs = require("fs-extra");
const path = require("path");
const minimist = require("minimist");
const webpack = require("webpack");
const webExt = require("web-ext");
const { ROOT
, INCLUDE_PATH
, DIST_PATH
, UNPACKED_PATH } = require("./lib/paths");
const packageMeta = require(`${ROOT}/../package.json`);
const extPackageMeta = require(`${ROOT}/package.json`);
const appPackageMeta = require(`${ROOT}/../app/package.json`);
const BRIDGE_NAME = "fx_cast_bridge";
const BRIDGE_VERSION = "0.1.0";
const MIRRORING_APP_ID = "19A6F4AE";
const argv = minimist(process.argv.slice(2), {
boolean: [ "package", "watch" ]
, string: [ "mirroringAppId", "mode" ]
, default: {
package: false // Should package with web-ext
, watch: false // Should run webpack in watch mode
, mirroringAppId: extPackageMeta.__mirroringAppId // Chromecast receiver app ID
, mode: "development" // webpack mode
package: false
, watch: false
, mirroringAppId: MIRRORING_APP_ID
, mode: "development"
}
});
@@ -38,107 +35,161 @@ if (argv.package) {
}
// Import webpack config and specify env values
const webpackConfig = require(`${ROOT}/webpack.config.js`)({
includePath: INCLUDE_PATH
// Paths
const rootPath = path.resolve(__dirname, "../");
const srcPath = path.join(rootPath, "src");
const distPath = path.join(rootPath, "../dist/ext/");
const unpackedPath = path.join(distPath, "unpacked");
const outPath = argv.package ? unpackedPath : distPath;
/** @type esbuild.Plugin */
const preactCompatPlugin = {
/**
* If watching files, output directly to dist. Unpacked
* directory is used as a staging area for web-ext builds.
* Handle react/react-dom preact compat modules.
*/
, outputPath: argv.package
? UNPACKED_PATH
: DIST_PATH
name: "preact-compat",
setup(build) {
const preactPath = path.resolve(__dirname
, "../node_modules/preact/compat/dist/compat.module.js");
, extensionName: extPackageMeta.__extensionName
, extensionId: extPackageMeta.__extensionId
, extensionVersion: extPackageMeta.__extensionVersion
, applicationName: appPackageMeta.__applicationName
, applicationVersion: appPackageMeta.__applicationVersion
, mirroringAppId: argv.mirroringAppId
// eval source map needs special CSP
, contentSecurityPolicy: argv.mode === "production"
? "script-src 'self'; object-src 'self'"
: "script-src 'self' 'unsafe-eval'; object-src 'self'"
// Developer info
, author: packageMeta.author
, authorHomepage: packageMeta.homepage
});
// Add mode to config
webpackConfig.mode = argv.mode;
if (argv.mode !== "production") {
webpackConfig.devtool = "source-map";
build.onResolve(
{ filter: /^(react|react-dom)$/ }
, (args) => ({ path: preactPath }));
}
}
/** @type esbuild.BuildOptions */
const buildOpts = {
bundle: true
, target: "firefox64"
, logLevel: "info"
, sourcemap: "inline"
, outdir: outPath
, outbase: srcPath
, entryPoints: [
// Main
`${srcPath}/background/background.ts`
// Media sender
, `${srcPath}/senders/media/index.ts`
, `${srcPath}/senders/media/overlay/overlayContent.ts`
, `${srcPath}/senders/media/overlay/overlayContentLoader.ts`
// Mirroring sender
, `${srcPath}/senders/mirroring.ts`
// Shim
, `${srcPath}/shim/index.ts`
, `${srcPath}/shim/content.ts`
, `${srcPath}/shim/contentBridge.ts`
// UI
, `${srcPath}/ui/popup/index.tsx`
, `${srcPath}/ui/options/index.tsx`
]
, define: {
BRIDGE_NAME: `"${BRIDGE_NAME}"`
, BRIDGE_VERSION: `"${BRIDGE_VERSION}"`
, MIRRORING_APP_ID: `"${argv.mirroringAppId}"`
}
, plugins: [ preactCompatPlugin ]
};
// Set production options
if (argv.mode === "production") {
buildOpts.minify = true;
buildOpts.sourcemap = false;
}
/**
* Handle build results.
*
* @param {esbuild.BuildResult} result
*/
function onBuildResult(result) {
if (result.errors.length) {
console.error("Build error!");
return;
}
const manifest = JSON.parse(
fs.readFileSync(`${srcPath}/manifest.json`
, { encoding: "utf-8" }));
manifest.content_security_policy = argv.mode === "production"
? "script-src 'self'; object-src 'self'"
: "script-src 'self' 'unsafe-eval'; object-src 'self'";
fs.writeFileSync(`${outPath}/manifest.json`, JSON.stringify(manifest));
copy(srcPath, outPath, /^(manifest\.json|.*\.(ts|tsx|js|jsx))$/);
}
/**
* Recursively copy directory contents.
*
* @param {string} src Source path
* @param {string} dest Destination path
* @param {RegExp} excludeRegex Match for file exclusion
*/
function copy(src, dest, excludeRegex) {
if (!fs.existsSync(src)) return;
const stats = fs.statSync(src);
if (!stats.isDirectory()) {
const dirName = path.dirname(dest);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName , { recursive: true });
}
fs.copyFileSync(src, dest);
return;
}
for (const file of fs.readdirSync(src)) {
if (excludeRegex.test(file)) continue;
copy(path.join(src, file), path.join(dest, file), excludeRegex);
}
}
// Clean
fs.removeSync(DIST_PATH);
// Create webpack compiler instance
const compiler = webpack(webpackConfig);
fs.removeSync(distPath);
if (argv.watch) {
// Start webpack watch
compiler.watch({}, handleCompilerOutput);
esbuild.build({
...buildOpts
, watch: {
onRebuild(_err, result) {
return onBuildResult(result);
}
}
}).then(onBuildResult);
} else {
compiler.run((...args) => {
handleCompilerOutput(...args);
esbuild.build(buildOpts).then(result => {
onBuildResult(result);
if (argv.package) {
webExt.cmd.build({
/**
* Webpack output at sourceDir is built into an extension
* archive at artifactsDir.
*/
sourceDir: UNPACKED_PATH
, artifactsDir: DIST_PATH
* Webpack output at sourceDir is built into an extension
* archive at artifactsDir.
*/
sourceDir: unpackedPath
, artifactsDir: distPath
, overwriteDest: true
}, {
// Prevent auto-exit
shouldExitProgram: false
}).then(result => {
const outputName = path.basename(result.extensionPath);
// Rename output extension to XPI
fs.moveSync(path.join(DIST_PATH, outputName)
, path.join(DIST_PATH, outputName.replace("zip", "xpi")));
fs.moveSync(path.join(distPath, outputName)
, path.join(distPath, outputName.replace(
"zip", "xpi")));
// Only need the built extension archive
fs.remove(UNPACKED_PATH);
fs.remove(unpackedPath);
});
}
});
}
/**
* Log errors and output formatted compilation info.
*/
function handleCompilerOutput (err, stats) {
// If there are fatal errors, log and exit
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
return;
}
// Get compilation info
const info = stats.toJson();
// Log errors/warnings
if (stats.hasErrors()) {
console.error(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
// Log formatted output
console.log(stats.toString());
}

View File

@@ -1,8 +0,0 @@
"use strict";
const path = require("path");
exports.ROOT = path.resolve(__dirname, "../../");
exports.INCLUDE_PATH = path.resolve(exports.ROOT, "src");
exports.DIST_PATH = path.join(exports.ROOT, "../dist/ext");
exports.UNPACKED_PATH = path.join(exports.DIST_PATH, "unpacked");

8618
ext/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,4 @@
{
"__extensionName": "fx_cast",
"__extensionId": "fx_cast@matt.tf",
"__extensionVersion": "0.1.2",
"__mirroringAppId": "19A6F4AE",
"scripts": {
"build": "node bin/build.js",
"package": "node bin/build.js --package",
@@ -12,14 +8,14 @@
},
"devDependencies": {
"@types/firefox-webext-browser": "^82.0.0",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@types/react": "^17.0.7",
"@types/react-dom": "^17.0.5",
"@types/semver": "^7.3.4",
"@types/uuid": "^8.3.0",
"copy-webpack-plugin": "^7.0.0",
"esbuild": "^0.12.3",
"html-webpack-plugin": "^5.2.0",
"preact": "^10.5.12",
"preact-compat": "^3.19.0",
"preact": "^10.5.13",
"semver": "^7.3.4",
"ts-loader": "^8.0.17",
"typescript": "^4.1.5",

View File

@@ -1,6 +1,6 @@
{
"extensionName": {
"message": "EXTENSION_NAME"
"message": "fx_cast"
, "description": "Name of the extension and the native receiver selector window title."
}
, "extensionDescription": {

View File

@@ -144,7 +144,7 @@ browser.menus.onClicked.addListener(async (info, tab) => {
});
await browser.tabs.executeScript(tab.id, {
file: "senders/media/bundle.js"
file: "senders/media/index.js"
, frameId: info.frameId
});
} else {

View File

@@ -178,7 +178,7 @@ async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
});
return {
redirectUrl: browser.runtime.getURL("shim/bundle.js")
redirectUrl: browser.runtime.getURL("shim/index.js")
};
}

View File

@@ -5,7 +5,7 @@ import { Options } from "./lib/options";
export default {
bridgeApplicationName: APPLICATION_NAME
bridgeApplicationName: BRIDGE_NAME
, bridgeBackupEnabled: false
, bridgeBackupHost: "localhost"
, bridgeBackupPort: 9556

6
ext/src/global.d.ts vendored
View File

@@ -1,8 +1,6 @@
// Define replacement types
declare const BRIDGE_VERSION: string;
declare const BRIDGE_NAME: string;
declare const MIRRORING_APP_ID: string;
declare const APPLICATION_NAME: string;
declare const APPLICATION_VERSION: string;
declare type Nullable<T> = T | null;

View File

@@ -101,7 +101,7 @@ const getInfo = () => new Promise<BridgeInfo>(async (resolve, reject) => {
// Print compatibility info to console
if (!isVersionCompatible) {
logger.error(`Expecting ${applicationName} v${APPLICATION_VERSION}, found v${applicationVersion}. ${
logger.error(`Expecting ${applicationName} v${BRIDGE_VERSION}, found v${applicationVersion}. ${
isVersionOlder
? "Try updating the native app to the latest version."
: "Try updating the extension to the latest version"}`);
@@ -110,7 +110,7 @@ const getInfo = () => new Promise<BridgeInfo>(async (resolve, reject) => {
resolve({
name: applicationName
, version: applicationVersion
, expectedVersion: APPLICATION_VERSION
, expectedVersion: BRIDGE_VERSION
// Version info
, isVersionExact

View File

@@ -1,15 +1,15 @@
{
"name": "__MSG_extensionName__"
, "description": "__MSG_extensionDescription__"
, "version": "EXTENSION_VERSION"
, "version": "0.1.2"
, "developer": {
"name": "AUTHOR"
, "url": "AUTHOR_HOMEPAGE"
"name": "Matt Hensman"
, "url": "https://matt.tf/"
}
, "applications": {
"gecko": {
"id": "EXTENSION_ID"
"id": "fx_cast@matt.tf"
, "strict_min_version": "64.0"
, "update_url": "https://hensm.github.io/fx_cast/updates.json"
}
@@ -25,7 +25,7 @@
}
, "background": {
"scripts": [ "background.js" ]
"scripts": [ "background/background.js" ]
}
, "content_scripts": [
@@ -39,7 +39,6 @@
}
]
, "content_security_policy": "CONTENT_SECURITY_POLICY"
, "default_locale": "en"
, "icons": {
@@ -66,7 +65,7 @@
, "<all_urls>"
]
, "web_accessible_resources": [
"shim/bundle.js"
"shim/index.js"
, "senders/media/overlay/overlayContent.js"
, "senders/media/overlay/AirPlay_Audio.svg"
, "senders/media/overlay/AirPlay_Video.svg"

View File

@@ -133,8 +133,8 @@ class OptionsApp extends Component<
return (
<div>
<form id="form" ref={ form => { this.form = form; }}
onSubmit={ this.handleFormSubmit }
onChange={ this.handleFormChange }>
onSubmit={ this.handleFormSubmit }
onChange={ this.handleFormChange }>
<Bridge info={ this.state.bridgeInfo }
loading={ this.state.bridgeLoading }

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../photon-colors.css">
<link rel="stylesheet" href="../photon-widgets.css">
<link rel="stylesheet" href="styles/index.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -1,116 +0,0 @@
"use strict";
const path = require("path");
const webpack = require("webpack");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const sourceFileExtensions = [
".js", ".jsx"
, ".ts", ".tsx"
];
module.exports = (env) => ({
entry: {
"background": `${env.includePath}/background/background.ts`
// Sender apps
, "senders/media/bundle": `${env.includePath}/senders/media/index.ts`
, "senders/media/overlay/overlayContent": `${env.includePath}/senders/media/overlay/overlayContent.ts`
, "senders/media/overlay/overlayContentLoader": `${env.includePath}/senders/media/overlay/overlayContentLoader.ts`
, "senders/mirroring": `${env.includePath}/senders/mirroring.ts`
// Shim entries
, "shim/bundle": `${env.includePath}/shim/index.ts`
, "shim/content": `${env.includePath}/shim/content.ts`
, "shim/contentBridge": `${env.includePath}/shim/contentBridge.ts`
// UI
, "ui/popup/bundle": `${env.includePath}/ui/popup/index.tsx`
, "ui/options/bundle": `${env.includePath}/ui/options/index.tsx`
}
, output: {
filename: "[name].js"
, path: env.outputPath
}
, plugins: [
new webpack.DefinePlugin({
"MIRRORING_APP_ID": JSON.stringify(env.mirroringAppId)
, "APPLICATION_NAME": JSON.stringify(env.applicationName)
, "APPLICATION_VERSION": JSON.stringify(env.applicationVersion)
})
// Copy static assets
, new CopyWebpackPlugin({
patterns: [
{
from: env.includePath
, to: env.outputPath
, globOptions: {
ignore: sourceFileExtensions.map(ext => `**${ext}`)
}
, transform (content, path) {
// Access to variables in static files
if (path.endsWith(".json")) {
return Buffer.from(content.toString()
.replace("EXTENSION_NAME", env.extensionName)
.replace("EXTENSION_ID", env.extensionId)
.replace("EXTENSION_VERSION", env.extensionVersion)
.replace("CONTENT_SECURITY_POLICY", env.contentSecurityPolicy)
.replace("AUTHOR", env.author)
.replace("AUTHOR_HOMEPAGE", env.authorHomepage));
}
return content;
}
}
]
})
, new HtmlWebpackPlugin({
inject: true
, template: `${env.includePath}/ui/template.html`
, filename: `${env.outputPath}/ui/popup/index.html`
, chunks: [ "ui/popup/bundle" ]
})
, new HtmlWebpackPlugin({
inject: true
, template: `${env.includePath}/ui/template.html`
, filename: `${env.outputPath}/ui/options/index.html`
, chunks: [ "ui/options/bundle" ]
})
]
, optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/](preact|preact\/compat)[\\/]/
, name: "vendor"
, chunks: "initial"
}
}
}
}
, module: {
rules: [
{
test: /\.(js|ts)x?$/
, resolve: {
extensions: sourceFileExtensions
}
, include: env.includePath
, use: {
loader: "ts-loader"
}
}
]
}
, resolve: {
alias: {
"react": "preact/compat"
, "react-dom": "preact/compat"
}
}
});

2045
package-lock.json generated

File diff suppressed because it is too large Load Diff