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 node_modules
tmp tmp
dist dist
dist-firefox
yarn-error.log yarn-error.log
/vscode-icons /vscode-icons
firefox-profile firefox-profile

View file

@ -9,9 +9,6 @@ update-icons:
node scripts/vscode-icons/resolve-languages-map node scripts/vscode-icons/resolve-languages-map
node scripts/vscode-icons/generate-icon-index node scripts/vscode-icons/generate-icon-index
clean:
rm -rf dist
build: build:
yarn build yarn build
@ -28,40 +25,23 @@ upload-for-analytics:
yarn sentry-cli releases finalize "$(FULL_VERSION)" yarn sentry-cli releases finalize "$(FULL_VERSION)"
compress: compress:
cd dist && zip -r Gitako-$(FULL_VERSION).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
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
compress-source: compress-source:
git archive -o dist/Gitako-$(FULL_VERSION)-source.zip HEAD git archive -o dist/Gitako-$(FULL_VERSION)-source.zip HEAD
zip dist/Gitako-$(FULL_VERSION)-source.zip .env zip dist/Gitako-$(FULL_VERSION)-source.zip .env
zip -r dist/Gitako-$(FULL_VERSION)-source.zip vscode-icons/icons 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: release:
$(MAKE) clean
$(MAKE) build $(MAKE) build
$(MAKE) test $(MAKE) test
$(MAKE) upload-for-analytics $(MAKE) upload-for-analytics
$(MAKE) compress $(MAKE) compress
$(MAKE) patch-firefox-manifest
$(MAKE) compress-firefox
$(MAKE) compress-source $(MAKE) compress-source
$(MAKE) copy-build-safari
release-dry-run: release-dry-run:
$(MAKE) clean
$(MAKE) build $(MAKE) build
$(MAKE) test $(MAKE) test
$(MAKE) compress $(MAKE) compress
$(MAKE) patch-firefox-manifest
$(MAKE) compress-firefox
$(MAKE) compress-source $(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). - (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 - 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: ### 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: ### Firefox:
- run `yarn dev-firefox` 1. run `yarn dev`
- a new instance of Firefox will open with Gitako automatically installed this will build Gitako with special Firefox configurations
- navigate to a GitHub repo and you should see the extension appear 1. run `yarn debug-firefox`
- when you modify source, refresh the tab 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): ### Safari (macOS only):
- run `yarn dev-safari` 1. run `yarn dev`
- Open `Safari/Gitako/Gitako.xcodeproj` in Xcode 1. Open `Safari/Gitako/Gitako.xcodeproj` in Xcode
- Click the "Run" button 1. Click the "Run" button
- Enable developer mode in Safari's preferences 1. Enable developer mode in Safari's preferences
- Enable Gitako in Safari's preferences 1. 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 1. 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. 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" "node": ">=20"
}, },
"scripts": { "scripts": {
"dev": "NODE_OPTIONS=--openssl-legacy-provider VERSION=dev-v$(node scripts/get-version.js) webpack --watch", "dev": "VERSION=dev-v$(node scripts/get-version.js) NODE_OPTIONS=--openssl-legacy-provider webpack --watch",
"dev-safari": "TARGET=safari yarn run dev", "debug-firefox": "web-ext run --source-dir=dist-firefox --keep-profile-changes --start-url https://github.com/EnixCoda/Gitako",
"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",
"prepare": "husky install", "prepare": "husky install",
"postinstall": "patch-package", "postinstall": "patch-package",
"postversion": "sh scripts/post-version.sh", "postversion": "sh scripts/post-version.sh",
"build": "NODE_OPTIONS=--openssl-legacy-provider VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack", "build": "VERSION=v$(node scripts/get-version.js) NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack",
"roll": "make release", "build:analyze": "ANALYZE= yarn run build",
"test": "NODE_ENV=test jest --config __tests__/jest.puppeteer.config.js", "test": "NODE_ENV=test jest --config __tests__/jest.puppeteer.config.js",
"test:unit": "NODE_ENV=test jest --config jest.config.js" "test:unit": "NODE_ENV=test jest --config jest.config.js"
}, },
@ -39,9 +37,9 @@
"react-window": "^1.8.7", "react-window": "^1.8.7",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"superstruct": "^1.0.3", "superstruct": "^1.0.3",
"webext-dynamic-content-scripts": "^9.0.0", "webext-dynamic-content-scripts": "^10.0.1",
"webext-permission-toggle": "^5.0.2", "webext-permission-toggle": "^5.0.2",
"webextension-polyfill": "^0.10.0" "webextension-polyfill": "^0.11.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.17.6", "@babel/cli": "^7.17.6",
@ -53,7 +51,7 @@
"@babel/preset-typescript": "^7.16.7", "@babel/preset-typescript": "^7.16.7",
"@sentry/cli": "^1.64.2", "@sentry/cli": "^1.64.2",
"@testing-library/react": "^13.3.0", "@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/history": "^5.0.0",
"@types/ini": "^1.3.31", "@types/ini": "^1.3.31",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
@ -69,10 +67,11 @@
"@typescript-eslint/parser": "^5.33.1", "@typescript-eslint/parser": "^5.33.1",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "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", "css-loader": "^2.1.0",
"dotenv": "^6.2.0", "dotenv": "^6.2.0",
"dotenv-webpack": "^1.7.0", "dotenv-webpack": "^8.1.0",
"eslint": "^8.15.0", "eslint": "^8.15.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.29.4",
@ -85,7 +84,7 @@
"jest-puppeteer": "^10.0.1", "jest-puppeteer": "^10.0.1",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^2.9.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"prettier": "^2.8.3", "prettier": "^2.8.3",
"puppeteer": "^22.12.1", "puppeteer": "^22.12.1",
@ -95,7 +94,7 @@
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"url-loader": "^1.1.2", "url-loader": "^1.1.2",
"web-ext": "^7.1.1", "web-ext": "^7.11.0",
"webpack": "^5.91.0", "webpack": "^5.91.0",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4" "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 'webext-dynamic-content-scripts'
import addPermissionToggle from 'webext-permission-toggle'
addPermissionToggle({ addPermissionToggle({
title: 'Enable Gitako on this domain', 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' import type { Configuration } from 'webpack'
const webpack = require('webpack') const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin') const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const path = require('path')
const Dotenv = require('dotenv-webpack') const Dotenv = require('dotenv-webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const { CleanWebpackPlugin } = require('clean-webpack-plugin')
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 IN_PRODUCTION_MODE = process.env.NODE_ENV === 'production' 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 function createConfig({ envTarget }: { envTarget: 'default' | 'firefox' | 'safari' }) {
if (buildTarget === 'safari') { const outputPath = {
Reflect.deleteProperty(manifest, 'optional_permissions') default: path.resolve(__dirname, 'dist'),
Reflect.deleteProperty(manifest, 'background') firefox: path.resolve(__dirname, 'dist-firefox'),
} safari: path.resolve(__dirname, 'Safari/Gitako/Gitako Extension/Resources'),
}[envTarget]
if (!IN_PRODUCTION_MODE) { const plugins = [
// enable source mapping while developing new CleanWebpackPlugin(),
Object.assign(manifest, { new CopyWebpackPlugin({
web_accessible_resources: manifest.web_accessible_resources.concat({ patterns: [
resources: ['*.map'], (() => {
matches: ['*://*/*'], 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 {
return JSON.stringify(manifest) from: './src/manifest.json',
}, to: 'manifest.json',
}, transform(content: string) {
{ const {
from: './src/assets/icons/*', version,
to: 'icons/[name].[ext]', description,
}, author,
{ homepage: homepage_url,
from: './vscode-icons/icons/*', } = require('./package.json')
to: 'icons/vscode/[name].[ext]', const manifest = JSON.parse(content)
}, s.assert(manifest, sManifest)
{
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(),
]
const analyze = process.env.ANALYZE !== undefined manifest.version = version
if (analyze) { manifest.description = description
plugins.push(new BundleAnalyzerPlugin()) manifest.author = author
console.log(`BundleAnalyzerPlugin added`) 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( const configs = (['default', 'firefox', 'safari'] as const).map(envTarget =>
new webpack.DefinePlugin({ createConfig({ envTarget }),
'process.env.VERSION': JSON.stringify(process.env.VERSION),
}),
) )
const webpackConfig: Configuration = { // Enable parallelism for faster build
entry: { // https://webpack.js.org/configuration/configuration-types/#parallelism
content: './src/content.tsx', Object.assign(configs, {
background: './src/background.ts', parallelism: true,
}, })
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,
}
module.exports = webpackConfig module.exports = configs

764
yarn.lock

File diff suppressed because it is too large Load diff