[frontend] change bundler to skulk (#942)

* replace web bundler with skulk

* upgrade skulk

* add license
This commit is contained in:
f0x52 2022-11-02 16:31:43 +01:00 committed by GitHub
parent f81f1e7d0f
commit c4c713988a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 897 additions and 2161 deletions

View file

@ -46,7 +46,6 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
assetsPathPrefix + "/dist/settings-style.css", assetsPathPrefix + "/dist/settings-style.css",
}, },
"javascript": []string{ "javascript": []string{
assetsPathPrefix + "/dist/react-bundle.js",
assetsPathPrefix + "/dist/settings.js", assetsPathPrefix + "/dist/settings.js",
}, },
}) })

View file

@ -22,6 +22,7 @@
"context" "context"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
@ -44,6 +45,12 @@ func NewTestRouter(db db.DB) router.Router {
config.SetBindAddress(alternativeBindAddress) config.SetBindAddress(alternativeBindAddress)
} }
if alternativePortStr := os.Getenv("GTS_PORT"); alternativePortStr != "" {
if alternativePort, err := strconv.Atoi(alternativePortStr); err == nil {
config.SetPort(alternativePort)
}
}
r, err := router.New(context.Background(), db) r, err := router.New(context.Background(), db)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -51,7 +51,7 @@ $error-link: #185F8C; /* Error link text, can be used with $error2 (5.54) */
$fg: $white1; $fg: $white1;
$bg: $gray1; $bg: $gray1;
$bg-trans: color-mod($gray5 alpha(62%)); $bg-trans: rgba(77, 78, 86, 0.62);
$bg-accent: $gray5; $bg-accent: $gray5;
$fg-accent: $blue3; $fg-accent: $blue3;

View file

@ -30,10 +30,10 @@
src: url(../NotoSans-Bold.ttf) format('truetype'); src: url(../NotoSans-Bold.ttf) format('truetype');
} }
// standard border radius for nice squircles /* standard border radius for nice squircles */
$br: 0.4rem; $br: 0.4rem;
// border radius for items that are framed/bordered /* border radius for items that are framed/bordered
// inside something with $br, eg avatar, header img inside something with $br, eg avatar, header img */
$br-inner: 0.2rem; $br-inner: 0.2rem;
html, body { html, body {

View file

@ -18,47 +18,70 @@
"use strict"; "use strict";
/* const skulk = require("skulk");
Bundle the PostCSS stylesheets and javascript bundles for general frontend and settings panel const fs = require("fs");
*/ const path = require("path");
const path = require('path'); let cssEntryFiles = fs.readdirSync(path.join(__dirname, "./css")).map((file) => {
const fsSync = require("fs");
const chalk = require("chalk");
const gtsBundler = require("./lib/bundler");
const devMode = process.env.NODE_ENV == "development";
if (devMode) {
console.log(chalk.yellow("GoToSocial web asset bundler, running in development mode"));
} else {
console.log(chalk.yellow("GoToSocial web asset bundler, creating production build"));
process.env.NODE_ENV = "production";
}
let cssFiles = fsSync.readdirSync(path.join(__dirname, "./css")).map((file) => {
return path.join(__dirname, "./css", file); return path.join(__dirname, "./css", file);
}); });
const bundles = [ const prodCfg = {
{ transform: [
outputFile: "frontend.js", ["uglifyify", {
entryFiles: ["./frontend/index.js"],
babelOptions: {
global: true, global: true,
exclude: /node_modules\/(?!photoswipe-dynamic-caption-plugin)/, exts: ".js"
} }],
}, ["@browserify/envify", {global: true}]
{ ]
outputFile: "react-bundle.js", };
factors: {
"./settings/index.js": "settings.js",
}
},
{
outputFile: "_delete", // not needed, we only care for the css that's already split-out by css-extract
entryFiles: cssFiles,
}
];
return gtsBundler(devMode, bundles); skulk({
name: "GoToSocial",
basePath: __dirname,
assetPath: "../assets/",
prodCfg: {
servers: {
express: false,
livereload: false
}
},
servers: {
express: {
proxy: "http://localhost:8081",
assets: "/assets"
}
},
bundles: {
frontend: {
entryFile: "frontend",
outputFile: "frontend.js",
preset: ["js"],
prodCfg: prodCfg,
transform: [
["babelify", {
global: true,
ignore: [/node_modules\/(?!(photoswipe.*))/]
}]
],
},
settings: {
entryFile: "settings",
outputFile: "settings.js",
prodCfg: prodCfg,
presets: [
"react",
["postcss", {
output: "settings-style.css"
}]
]
},
css: {
entryFiles: cssEntryFiles,
outputFile: "_discard",
presets: [["postcss", {
output: "_split"
}]]
}
}
});

View file

@ -1,200 +0,0 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const Promise = require("bluebird");
const browserify = require("browserify");
const babelify = require('babelify');
const chalk = require("chalk");
const fs = require("fs").promises;
const { EventEmitter } = require("events");
const path = require("path");
const debugLib = require("debug");
debugLib.enable("GoToSocial");
const debug = debugLib("GoToSocial");
const outputEmitter = new EventEmitter();
const splitCSS = require("./split-css")(outputEmitter);
const out = require("./output-path");
const postcssPlugins = [
"postcss-import",
"postcss-nested",
"autoprefixer",
"postcss-custom-prop-vars",
"postcss-color-mod-function"
].map((plugin) => require(plugin)());
function browserifyConfig(devMode, { transforms = [], plugins = [], babelOptions = {} }) {
if (devMode) {
plugins.push(require("watchify"));
} else {
transforms.push([
require("uglifyify"), {
global: true,
exts: ".js"
}
]);
}
return {
cache: {},
packageCache: {},
transform: [
[
babelify.configure({
presets: [
[
require.resolve("@babel/preset-env"),
{
modules: "cjs"
}
],
require.resolve("@babel/preset-react")
]
}),
babelOptions
],
...transforms
],
plugin: [
[require("icssify"), {
parser: require("postcss-scss"),
before: postcssPlugins,
mode: 'global'
}],
[require("css-extract"), { out: splitCSS }],
...plugins
],
extensions: [".js", ".jsx", ".css"],
basedir: path.join(__dirname, "../"),
fullPaths: devMode,
debug: devMode
};
}
module.exports = function gtsBundler(devMode, bundles) {
if (devMode) {
require("./dev-server")(outputEmitter);
}
Promise.each(bundles, (bundleCfg) => {
let transforms, plugins, entryFiles;
let { outputFile, babelOptions } = bundleCfg;
if (bundleCfg.factors != undefined) {
let factorBundle = [require("factor-bundle"), {
outputs: Object.values(bundleCfg.factors).map((file) => {
return out(file);
}),
threshold: function(row, groups) {
// always put livereload.js in common bundle
if (row.file.endsWith("web/source/lib/livereload.js")) {
return true;
} else {
return this._defaultThreshold(row, groups);
}
}
}];
plugins = [factorBundle];
entryFiles = Object.keys(bundleCfg.factors);
} else {
entryFiles = bundleCfg.entryFiles;
}
if (devMode) {
entryFiles.push(path.join(__dirname, "./livereload.js"));
}
let config = browserifyConfig(devMode, { transforms, plugins, babelOptions, entryFiles, outputFile });
return Promise.try(() => {
return browserify(entryFiles, config);
}).then((bundler) => {
bundler.on("error", (err) => {
console.error(err.message);
});
Promise.promisifyAll(bundler);
function makeBundle(cause) {
if (cause != undefined) {
debug(chalk.yellow(`Watcher: update on ${cause}, re-bundling`));
}
return Promise.try(() => {
return bundler.bundleAsync();
}).then((bundle) => {
if (outputFile != "_delete") {
let updates = new Set([outputFile]);
if (bundleCfg.factors != undefined) {
Object.values(bundleCfg.factors).forEach((factor) => {
updates.add(factor);
debug(chalk.magenta(`JS: writing to assets/dist/${factor}`));
});
}
outputEmitter.emit("update", {type: "JS", updates: Array.from(updates)});
return fs.writeFile(out(outputFile), bundle);
}
}).catch((e) => {
debug(chalk.red("Fatal error in bundler:"), bundleCfg.outputFile);
if (e.name == "CssSyntaxError") {
// contains useful info about error + location, but followed by useless
// actual stacktrace, so cut that off
let stack = e.stack;
stack.split("\n").some((line) => {
if (line.startsWith(" at Input.error")) {
return true;
} else {
debug(line);
return false;
}
});
} else {
debug(e.message);
}
});
}
if (devMode) {
bundler.on("update", makeBundle);
}
return makeBundle();
});
}).then(() => {
if (devMode) {
debug(chalk.yellow("Initial build finished, waiting for file changes"));
} else {
debug(chalk.yellow("Finished building"));
}
});
};
outputEmitter.on("update", (u) => {
u.updates.forEach((outputFile) => {
let color = (str) => str;
if (u.type == "JS") {
color = chalk.magenta;
} else {
color = chalk.blue;
}
debug(color(`${u.type}: writing to assets/dist/${outputFile}`));
});
});

View file

@ -1,40 +0,0 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const tinylr = require("tiny-lr");
const chalk = require("chalk");
const PORT = 35729;
module.exports = function devServer(outputEmitter) {
let server = tinylr();
server.listen(PORT, () => {
console.log(chalk.cyan(`Livereload server listening on :${PORT}`));
});
outputEmitter.on("update", ({updates}) => {
let fullPaths = updates.map((path) => `/assets/dist/${path}`);
tinylr.changed(fullPaths.join(","));
});
process.on("SIGUSR2", server.close);
process.on("SIGTERM", server.close);
};

View file

@ -1,29 +0,0 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
window.LiveReloadOptions = {
host: 'localhost',
pluginOrder: "css,img,external",
verbose: true
};
console.log("Development bundle with Livereloading code");
require("livereload-js/dist/livereload.min.js");

View file

@ -1,32 +0,0 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const fsSync = require("fs");
const path = require("path");
function out(name = "") {
return path.join(__dirname, "../../assets/dist/", name);
}
if (!fsSync.existsSync(out())){
fsSync.mkdirSync(out(), { recursive: true });
}
module.exports = out;

View file

@ -1,82 +0,0 @@
/*
GoToSocial
Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const fs = require("fs");
const path = require("path");
const {Writable} = require("stream");
const out = require("./output-path");
const fromRegex = /\/\* from (.+?) \*\//;
module.exports = function splitCSS(outputEmitter) {
return function() {
let chunks = [];
return new Writable({
write: function(chunk, encoding, next) {
chunks.push(chunk);
next();
},
final: function() {
let stream = chunks.join("");
let input;
let content = [];
function write() {
if (content.length != 0) {
if (input == undefined) {
if (content[0].length != 0) {
throw new Error("Got CSS content without filename, can't output: ", content);
}
} else {
outputEmitter.emit("update", {type: "CSS", updates: [input]});
fs.writeFileSync(out(input), content.join("\n"));
}
content = [];
}
}
const cssDir = path.join(__dirname, "../css");
stream.split("\n").forEach((line) => {
if (line.startsWith("/* from")) {
let found = fromRegex.exec(line);
if (found != null) {
write();
let parts = path.parse(found[1]);
if (path.relative(cssDir, path.join(process.cwd(), parts.dir)) == "") {
input = parts.base;
} else {
// prefix filename with path
let relative = path.relative(path.join(__dirname, "../"), path.join(process.cwd(), found[1]));
input = relative.replace(/\//g, "-");
}
}
} else {
content.push(line);
}
});
write();
}
});
};
};

View file

@ -6,55 +6,39 @@
"author": "f0x", "author": "f0x",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@babel/core": "^7.12.13", "@reduxjs/toolkit": "^1.8.6",
"@babel/preset-env": "^7.12.13",
"@babel/preset-react": "^7.12.13",
"@f0x52/budo-express": "^1.1.0",
"@reduxjs/toolkit": "^1.8.5",
"autoprefixer": "^10.4.8",
"babelify": "^10.0.0",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"browserify": "^17.0.0",
"browserlist": "^1.0.1",
"chalk": "4",
"create-error": "^0.3.1",
"css-extract": "^2.0.0",
"default-value": "^1.0.0",
"dotty": "^0.1.2", "dotty": "^0.1.2",
"factor-bundle": "^2.5.0",
"icssify": "^2.0.0",
"is-plain-object": "^5.0.0",
"is-valid-domain": "^0.1.6", "is-valid-domain": "^0.1.6",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"langs": "^2.0.0", "langs": "^2.0.0",
"livereload-js": "^3.4.1",
"modern-normalize": "^1.1.0", "modern-normalize": "^1.1.0",
"photoswipe": "^5.3.0", "photoswipe": "^5.3.3",
"photoswipe-dynamic-caption-plugin": "^1.2.4", "photoswipe-dynamic-caption-plugin": "^1.2.7",
"postcss-color-mod-function": "^3.0.3", "react": "^18.2.0",
"postcss-custom-prop-vars": "^0.0.5", "react-dom": "^18.2.0",
"postcss-import": "^14.1.0",
"postcss-nested": "^5.0.6",
"postcss-scss": "^4.0.4",
"postcss-strip-inline-comments": "^0.1.5",
"prettier-bytes": "^1.0.4",
"pretty-bytes": "4",
"react": "18",
"react-dom": "18",
"react-error-boundary": "^3.1.4", "react-error-boundary": "^3.1.4",
"react-redux": "^8.0.2", "react-redux": "^8.0.4",
"redux": "^4.2.0",
"redux-devtools-extension": "^2.13.9", "redux-devtools-extension": "^2.13.9",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-thunk": "^2.4.1", "skulk": "^0.0.5",
"tiny-lr": "^2.0.0",
"uglifyify": "^5.0.2",
"watchify": "^4.0.0",
"wouter": "^2.8.0-alpha.2" "wouter": "^2.8.0-alpha.2"
}, },
"devDependencies": { "devDependencies": {
"@f0x52/eslint-config-react": "^1.1.0", "@babel/core": "^7.19.6",
"eslint": "^7.30.0", "@babel/preset-env": "^7.19.4",
"eslint-plugin-react": "^7.24.0", "@babel/preset-react": "^7.18.6",
"eslint-plugin-react-hooks": "^4.2.0" "@browserify/envify": "^6.0.0",
"autoprefixer": "^10.4.13",
"babelify": "^10.0.0",
"css-extract": "^2.0.0",
"factor-bundle": "^2.5.0",
"icssify": "^2.0.0",
"postcss": "^8.4.18",
"postcss-custom-prop-vars": "^0.0.5",
"postcss-import": "^15.0.0",
"postcss-nested": "^6.0.0",
"uglifyify": "^5.0.2"
} }
} }

View file

@ -42,6 +42,7 @@ const combinedReducers = combineReducers({
const persistedReducer = persistReducer(persistConfig, combinedReducers); const persistedReducer = persistReducer(persistConfig, combinedReducers);
const composedEnhancer = composeWithDevTools(applyMiddleware(thunk)); const composedEnhancer = composeWithDevTools(applyMiddleware(thunk));
// TODO: change to configureStore
const store = createStore(persistedReducer, composedEnhancer); const store = createStore(persistedReducer, composedEnhancer);
const persistor = persistStore(store); const persistor = persistStore(store);

File diff suppressed because it is too large Load diff