build: unified build process

This commit is contained in:
EnixCoda 2024-07-15 01:18:41 +08:00
parent 6570e54fce
commit cb72a51061
8 changed files with 530 additions and 711 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
node_modules
tmp
dist
dist-firefox
yarn-error.log
/vscode-icons
firefox-profile

View file

@ -9,9 +9,6 @@ update-icons:
node scripts/vscode-icons/resolve-languages-map
node scripts/vscode-icons/generate-icon-index
clean:
rm -rf dist
build:
yarn build
@ -28,40 +25,23 @@ upload-for-analytics:
yarn sentry-cli releases finalize "$(FULL_VERSION)"
compress:
cd dist && zip -r Gitako-$(FULL_VERSION).zip * -x *.map -x *.DS_Store -x*.zip
patch-firefox-manifest:
node scripts/patch-manifest-for-firefox.js
compress-firefox:
cd dist && zip -r Gitako-$(FULL_VERSION)-firefox.zip * -x *.map -x *.DS_Store -x*.zip
cd dist && zip -r Gitako-$(FULL_VERSION).zip * -x *.map -x *.DS_Store -x *.zip
cd dist-firefox && zip -r Gitako-$(FULL_VERSION)-firefox.zip * -x *.map -x *.DS_Store -x *.zip
compress-source:
git archive -o dist/Gitako-$(FULL_VERSION)-source.zip HEAD
zip dist/Gitako-$(FULL_VERSION)-source.zip .env
zip -r dist/Gitako-$(FULL_VERSION)-source.zip vscode-icons/icons
copy-build-safari:
rm -rf Safari/Gitako/Gitako\ Extension/Resources/*
cd dist && cp -r . ../Safari/Gitako/Gitako\ Extension/Resources
release:
$(MAKE) clean
$(MAKE) build
$(MAKE) test
$(MAKE) upload-for-analytics
$(MAKE) compress
$(MAKE) patch-firefox-manifest
$(MAKE) compress-firefox
$(MAKE) compress-source
$(MAKE) copy-build-safari
release-dry-run:
$(MAKE) clean
$(MAKE) build
$(MAKE) test
$(MAKE) compress
$(MAKE) patch-firefox-manifest
$(MAKE) compress-firefox
$(MAKE) compress-source
$(MAKE) copy-build-safari

View file

@ -18,27 +18,71 @@ When you modify source code, you need to do either of below to apply your change
- (recommended) use [the Extension Reloader extension](https://chrome.google.com/webstore/detail/fimgfedafeadlieiabdeeaodndnlbhid). It could reload all extensions then refresh the page (you need to enable it in its settings).
- manually reload the extension in the `chrome://extensions` and then refresh your repository page
## Develop with more browsers
## Develop with other browsers
Gitako supports more browsers, in order to develop for them, please do the followings.
Gitako supports Chrome, Edge, Firefox, and Safari. You can develop with other browsers by following the instructions below.
### Edge:
- Similar to above steps for Chrome
1. Finish the steps in "Set up development env" to `yarn dev`
1. Open the extensions page in Edge, enable developer mode, and load the extension from the `dist` folder
1. Navigate to repository of your choice and you should see the extension appear
### Firefox:
- run `yarn dev-firefox`
- a new instance of Firefox will open with Gitako automatically installed
- navigate to a GitHub repo and you should see the extension appear
- when you modify source, refresh the tab
1. run `yarn dev`
this will build Gitako with special Firefox configurations
1. run `yarn debug-firefox`
a new instance of Firefox will open with Gitako automatically installed
1. navigate to a GitHub repo and you should see the extension appear
if not, click the extension icon in the toolbar and enable Gitako in its submenu
1. when you modify source, better refresh the tab
### Safari (macOS only):
- run `yarn dev-safari`
- Open `Safari/Gitako/Gitako.xcodeproj` in Xcode
- Click the "Run" button
- Enable developer mode in Safari's preferences
- Enable Gitako in Safari's preferences
- Open a Safari tab and visit a GitHub repo, then activate Gitako via Gitako icon next to the address bar
- when you modify source, click the "Run" button in Xcode and refresh the tab
1. run `yarn dev`
1. Open `Safari/Gitako/Gitako.xcodeproj` in Xcode
1. Click the "Run" button
1. Enable developer mode in Safari's preferences
1. Enable Gitako in Safari's preferences
1. Open a Safari tab and visit a GitHub repo, then activate Gitako via Gitako icon next to the address bar
1. when you modify source, click the "Run" button in Xcode and refresh the tab
## Build for production
Run `make release` to build and release the extension
```mermaid
graph LR
source[Source Code] --> yarn-build[$ yarn build] --> dir-dist
yarn-build --> dir-dist-firefox
yarn-build --> dir-dist-safari
subgraph FileSystem
direction TB
dir-dist[./dist]
dir-dist-firefox[./dist-firefox]
dir-dist-safari[./Safari/Gitako/Gitako Extension/Resources]
end
dir-dist -- source maps --> sentry[Sentry]
dir-dist -- "exclude source maps" --> artifact[Gitako-$version.zip] --> release-target[Chrome Web Store]
artifact --> release-target-edge[Edge Add-ons]
dir-dist-firefox -- "exclude source maps" --> artifact-firefox[Gitako-$version-firefox.zip] --> release-target-firefox[Firefox Add-ons]
FileSystem -- source files --> artifact-source-firefox[Gitako-$version-source.zip] --> release-target-firefox[Firefox Add-ons]
dir-dist-safari --> xcode[Xcode Build] --> release-target-safari[App Store]
classDef release fill:#9ef
class sentry,release-target,release-target-edge,release-target-firefox,release-target-safari release
classDef command fill:#fe9
class yarn-dev command
classDef dir fill:#9f9
class dir-dist,dir-dist-firefox,dir-dist-safari dir
classDef artifact fill:#f9f
class artifact,artifact-firefox,artifact-source-firefox artifact
```

View file

@ -11,15 +11,13 @@
"node": ">=20"
},
"scripts": {
"dev": "NODE_OPTIONS=--openssl-legacy-provider VERSION=dev-v$(node scripts/get-version.js) webpack --watch",
"dev-safari": "TARGET=safari yarn run dev",
"dev-firefox": "web-ext run --source-dir=dist --firefox-profile=firefox-profile --profile-create-if-missing --keep-profile-changes --start-url https://github.com/EnixCoda/Gitako",
"analyze-bundle": "NODE_OPTIONS=--openssl-legacy-provider ANALYZE= NODE_ENV=production webpack",
"dev": "VERSION=dev-v$(node scripts/get-version.js) NODE_OPTIONS=--openssl-legacy-provider webpack --watch",
"debug-firefox": "web-ext run --source-dir=dist-firefox --keep-profile-changes --start-url https://github.com/EnixCoda/Gitako",
"prepare": "husky install",
"postinstall": "patch-package",
"postversion": "sh scripts/post-version.sh",
"build": "NODE_OPTIONS=--openssl-legacy-provider VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack",
"roll": "make release",
"build": "VERSION=v$(node scripts/get-version.js) NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack",
"build:analyze": "ANALYZE= yarn run build",
"test": "NODE_ENV=test jest --config __tests__/jest.puppeteer.config.js",
"test:unit": "NODE_ENV=test jest --config jest.config.js"
},
@ -39,9 +37,9 @@
"react-window": "^1.8.7",
"styled-components": "^5.3.5",
"superstruct": "^1.0.3",
"webext-dynamic-content-scripts": "^9.0.0",
"webext-dynamic-content-scripts": "^10.0.1",
"webext-permission-toggle": "^5.0.2",
"webextension-polyfill": "^0.10.0"
"webextension-polyfill": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.17.6",
@ -53,7 +51,7 @@
"@babel/preset-typescript": "^7.16.7",
"@sentry/cli": "^1.64.2",
"@testing-library/react": "^13.3.0",
"@types/firefox-webext-browser": "^70.0.1",
"@types/firefox-webext-browser": "^120.0.3",
"@types/history": "^5.0.0",
"@types/ini": "^1.3.31",
"@types/jest": "^29.5.12",
@ -69,10 +67,11 @@
"@typescript-eslint/parser": "^5.33.1",
"babel-loader": "^8.2.5",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"copy-webpack-plugin": "^5.0.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^2.1.0",
"dotenv": "^6.2.0",
"dotenv-webpack": "^1.7.0",
"dotenv-webpack": "^8.1.0",
"eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.29.4",
@ -85,7 +84,7 @@
"jest-puppeteer": "^10.0.1",
"json-loader": "^0.5.7",
"lint-staged": "^13.0.3",
"mini-css-extract-plugin": "^0.9.0",
"mini-css-extract-plugin": "^2.9.0",
"patch-package": "^8.0.0",
"prettier": "^2.8.3",
"puppeteer": "^22.12.1",
@ -95,7 +94,7 @@
"ts-node": "^10.9.2",
"typescript": "^5.5.3",
"url-loader": "^1.1.2",
"web-ext": "^7.1.1",
"web-ext": "^7.11.0",
"webpack": "^5.91.0",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4"

View file

@ -1,15 +0,0 @@
const path = require('path')
const fs = require('fs')
const manifestPath = path.join(__dirname, '..', 'dist', 'manifest.json')
const manifest = require(manifestPath)
manifest.manifest_version = 2
const serviceWorker = manifest.background?.['service_worker']
if (serviceWorker) {
console.log('Patching background')
manifest.background.scripts = [serviceWorker]
Reflect.deleteProperty(manifest.background, 'service_worker')
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2))
} else {
console.log('Service worker field not found, skipped patching')
}

View file

@ -1,5 +1,5 @@
import addPermissionToggle from 'webext-permission-toggle'
import 'webext-dynamic-content-scripts'
import addPermissionToggle from 'webext-permission-toggle'
addPermissionToggle({
title: 'Enable Gitako on this domain',

View file

@ -1,167 +1,207 @@
import * as path from 'node:path'
import * as s from 'superstruct'
import type { Configuration } from 'webpack'
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const path = require('path')
const Dotenv = require('dotenv-webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const srcPath = path.resolve(__dirname, 'src')
function resolvePathInput(input: string) {
return path.isAbsolute(input) ? input : path.resolve(process.cwd(), input)
}
const buildTarget = process.env.TARGET
const buildTargetOutputMap = {
safari: 'Safari/Gitako/Gitako Extension/Resources',
}
const envOutputDir =
process.env.OUTPUT_DIR ||
(buildTarget &&
buildTarget in buildTargetOutputMap &&
buildTargetOutputMap[buildTarget as keyof typeof buildTargetOutputMap])
const outputPath = envOutputDir ? resolvePathInput(envOutputDir) : path.resolve(__dirname, 'dist')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const IN_PRODUCTION_MODE = process.env.NODE_ENV === 'production'
const plugins = [
new CopyWebpackPlugin([
{
from: './src/manifest.json',
to: 'manifest.json',
transform(content: string) {
const { version, description, author, homepage: homepage_url } = require('./package.json')
const manifest = JSON.parse(content)
Object.assign(manifest, {
version,
description,
author,
homepage_url,
})
// Disable custom domains for Safari
if (buildTarget === 'safari') {
Reflect.deleteProperty(manifest, 'optional_permissions')
Reflect.deleteProperty(manifest, 'background')
}
function createConfig({ envTarget }: { envTarget: 'default' | 'firefox' | 'safari' }) {
const outputPath = {
default: path.resolve(__dirname, 'dist'),
firefox: path.resolve(__dirname, 'dist-firefox'),
safari: path.resolve(__dirname, 'Safari/Gitako/Gitako Extension/Resources'),
}[envTarget]
if (!IN_PRODUCTION_MODE) {
// enable source mapping while developing
Object.assign(manifest, {
web_accessible_resources: manifest.web_accessible_resources.concat({
resources: ['*.map'],
matches: ['*://*/*'],
const plugins = [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
(() => {
const sManifest = s.type({
version: s.optional(s.string()),
description: s.optional(s.string()),
author: s.optional(s.string()),
homepage_url: s.optional(s.string()),
manifest_version: s.number(),
background: s.type({
scripts: s.optional(s.array(s.string())),
service_worker: s.string(),
}),
web_accessible_resources: s.array(
s.type({
resources: s.array(s.string()),
matches: s.array(s.string()),
}),
),
})
}
return JSON.stringify(manifest)
},
},
{
from: './src/assets/icons/*',
to: 'icons/[name].[ext]',
},
{
from: './vscode-icons/icons/*',
to: 'icons/vscode/[name].[ext]',
},
{
from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js',
to: 'browser-polyfill.js',
},
{
from: './src/firefox-shim.js',
to: 'firefox-shim.js',
},
]),
new ForkTsCheckerWebpackPlugin(),
new Dotenv(),
new MiniCssExtractPlugin(),
]
return {
from: './src/manifest.json',
to: 'manifest.json',
transform(content: string) {
const {
version,
description,
author,
homepage: homepage_url,
} = require('./package.json')
const manifest = JSON.parse(content)
s.assert(manifest, sManifest)
const analyze = process.env.ANALYZE !== undefined
if (analyze) {
plugins.push(new BundleAnalyzerPlugin())
console.log(`BundleAnalyzerPlugin added`)
manifest.version = version
manifest.description = description
manifest.author = author
manifest.homepage_url = homepage_url
switch (envTarget) {
case 'safari': {
// Disable custom domains for Safari
Reflect.deleteProperty(manifest, 'optional_permissions')
Reflect.deleteProperty(manifest, 'background')
break
}
case 'firefox': {
manifest.manifest_version = 3
// Firefox does not support service worker
Reflect.set(manifest.background, 'scripts', [manifest.background.service_worker])
Reflect.deleteProperty(manifest.background, 'service_worker')
break
}
}
if (!IN_PRODUCTION_MODE) {
// enable source mapping while developing
manifest.web_accessible_resources.push({
resources: ['*.map'],
matches: ['*://*/*'],
})
}
return JSON.stringify(manifest)
},
}
})(),
{
from: './src/assets/icons/*',
to: 'icons/[name][ext]',
},
{
from: './vscode-icons/icons/*',
to: 'icons/vscode/[name][ext]',
},
{
from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js',
to: 'browser-polyfill.js',
},
{
from: './src/firefox-shim.js',
to: 'firefox-shim.js',
},
],
}),
new ForkTsCheckerWebpackPlugin(),
new Dotenv(),
new MiniCssExtractPlugin(),
new webpack.DefinePlugin({
'process.env.VERSION': JSON.stringify(process.env.VERSION),
}),
]
const analyze = process.env.ANALYZE !== undefined
if (analyze) {
const webpackBundleAnalyzer = require('webpack-bundle-analyzer')
plugins.push(new webpackBundleAnalyzer.BundleAnalyzerPlugin())
console.log(`BundleAnalyzerPlugin added`)
}
const srcPath = path.resolve(__dirname, 'src')
const webpackConfig: Configuration = {
entry: {
content: './src/content.tsx',
background: './src/background.ts',
},
devtool: IN_PRODUCTION_MODE ? 'source-map' : 'inline-source-map',
mode: IN_PRODUCTION_MODE ? 'production' : 'development',
output: {
path: outputPath,
filename: '[name].js',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
modules: [srcPath, 'node_modules'],
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'babel-loader',
include: [srcPath],
exclude: /node_modules/,
sideEffects: false,
},
{
test: /\.[cm]?js$/,
loader: 'babel-loader',
// Transpile as least files under node_modules
include: /node_modules\/(webext-.*|superstruct)\/.*\.[cm]?js$/,
options: {
cacheDirectory: true,
},
},
{
test: /\.scss$/,
loader: MiniCssExtractPlugin.loader,
include: [srcPath],
},
{
test: /\.scss$/,
loader: 'css-loader',
include: [srcPath],
},
{
test: /\.scss$/,
loader: 'sass-loader',
include: [srcPath],
},
{
test: /\.svg$/,
resourceQuery: /inline/,
loader: 'url-loader',
},
{
test: /\.csv$/,
loader: 'raw-loader',
},
{
test: /\.json$/,
loader: 'json-loader',
include: [srcPath],
},
{
test: /\.png$/,
loader: 'url-loader',
include: [srcPath],
},
],
},
plugins,
}
return webpackConfig
}
plugins.push(
new webpack.DefinePlugin({
'process.env.VERSION': JSON.stringify(process.env.VERSION),
}),
const configs = (['default', 'firefox', 'safari'] as const).map(envTarget =>
createConfig({ envTarget }),
)
const webpackConfig: Configuration = {
entry: {
content: './src/content.tsx',
background: './src/background.ts',
},
devtool: IN_PRODUCTION_MODE ? 'source-map' : 'inline-source-map',
mode: IN_PRODUCTION_MODE ? 'production' : 'development',
output: {
path: outputPath,
filename: '[name].js',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
modules: [srcPath, 'node_modules'],
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'babel-loader',
include: [srcPath],
exclude: /node_modules/,
sideEffects: false,
},
{
test: /\.[cm]?js$/,
loader: 'babel-loader',
// Transpile as least files under node_modules
include: /node_modules\/(webext-.*|superstruct)\/.*\.[cm]?js$/,
options: {
cacheDirectory: true,
},
},
{
test: /\.scss$/,
loader: MiniCssExtractPlugin.loader,
include: [srcPath],
},
{
test: /\.scss$/,
loader: 'css-loader',
include: [srcPath],
},
{
test: /\.scss$/,
loader: 'sass-loader',
include: [srcPath],
},
{
test: /\.svg$/,
resourceQuery: /inline/,
loader: 'url-loader',
},
{
test: /\.csv$/,
loader: 'raw-loader',
},
{
test: /\.json$/,
loader: 'json-loader',
include: [srcPath],
},
{
test: /\.png$/,
loader: 'url-loader',
include: [srcPath],
},
],
},
plugins,
}
// Enable parallelism for faster build
// https://webpack.js.org/configuration/configuration-types/#parallelism
Object.assign(configs, {
parallelism: true,
})
module.exports = webpackConfig
module.exports = configs

764
yarn.lock

File diff suppressed because it is too large Load diff