2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 10:58:59 +00:00

build: create app protocol

- add a build script (wip not yet packaged)
This commit is contained in:
18alantom 2023-06-20 19:29:25 +05:30
parent dde007dd09
commit 8df735d76f
10 changed files with 202 additions and 72 deletions

79
build/scripts/build.mjs Normal file
View File

@ -0,0 +1,79 @@
import vue from '@vitejs/plugin-vue';
import esbuild from 'esbuild';
import { $ } from 'execa';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import * as vite from 'vite';
import { getMainProcessCommonConfig } from './helpers.mjs';
const dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(dirname, '..', '..');
const $$ = $({ stdio: 'inherit' });
await $$`rm -rf ${path.join(root, 'dist_electron', 'build')}`;
await buildMainProcessSource();
await buildRendererProcessSource();
async function buildMainProcessSource() {
const commonConfig = getMainProcessCommonConfig(root);
const result = await esbuild.build({
...commonConfig,
outfile: path.join(root, 'dist_electron', 'build', 'main.js'),
});
if (result.errors.length) {
console.error('app build failed due to main process source build');
result.errors.forEach((err) => console.error(err));
process.exit(1);
}
}
async function buildRendererProcessSource() {
const base = 'app://';
const outDir = path.join(root, 'dist_electron', 'build', 'src');
await vite.build({
base: `/${base}`,
root: path.join(root, 'src'),
build: { outDir },
plugins: [vue()],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
fyo: path.join(root, 'fyo'),
src: path.join(root, 'src'),
schemas: path.join(root, 'schemas'),
backend: path.join(root, 'backend'),
models: path.join(root, 'models'),
utils: path.join(root, 'utils'),
regional: path.join(root, 'regional'),
reports: path.join(root, 'reports'),
dummy: path.join(root, 'dummy'),
fixtures: path.join(root, 'fixtures'),
},
},
});
removeBaseLeadingSlash(outDir, base);
}
/**
* Removes leading slash from all renderer files
* electron uses a custom registered protocol to load the
* files: "app://"
*
* @param {string} dir
* @param {string} base
*/
function removeBaseLeadingSlash(dir, base) {
for (const file of fs.readdirSync(dir)) {
const filePath = path.join(dir, file);
if (fs.lstatSync(filePath).isDirectory()) {
removeBaseLeadingSlash(filePath, base);
continue;
}
const contents = fs.readFileSync(filePath).toString('utf-8');
fs.writeFileSync(filePath, contents.replaceAll('/' + base, base));
}
}

View File

@ -3,7 +3,7 @@ import esbuild from 'esbuild';
import { $ } from 'execa'; import { $ } from 'execa';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { excludeVendorFromSourceMap } from './plugins.mjs'; import { getMainProcessCommonConfig } from './helpers.mjs';
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
process.env['NODE_ENV'] = 'development'; process.env['NODE_ENV'] = 'development';
@ -45,16 +45,8 @@ if (!process.argv.includes('--no-renderer')) {
* to [re]build the main process code * to [re]build the main process code
*/ */
const ctx = await esbuild.context({ const ctx = await esbuild.context({
entryPoints: [path.join(root, 'main.ts')], ...getMainProcessCommonConfig(root),
bundle: true, outfile: path.join(root, 'dist_electron', 'dev', 'main.js'),
sourcemap: true,
sourcesContent: false,
platform: 'node',
target: 'node16',
outfile: path.join(root, 'dist', 'dev', 'main.js'),
external: ['knex', 'electron', 'better-sqlite3'],
plugins: [excludeVendorFromSourceMap],
write: true,
}); });
/** /**
@ -135,7 +127,7 @@ async function handleResult(result) {
function runElectron() { function runElectron() {
const electronProcess = $$`npx electron --inspect=5858 ${path.join( const electronProcess = $$`npx electron --inspect=5858 ${path.join(
root, root,
'dist', 'dist_electron',
'dev', 'dev',
'main.js' 'main.js'
)}`; )}`;

51
build/scripts/helpers.mjs Normal file
View File

@ -0,0 +1,51 @@
import fs from 'fs';
import path from 'path';
/**
* Common ESBuild config used for building main process source
* code for both dev and production.
*
* @param {string} root
* @returns {import('esbuild').BuildOptions}
*/
export function getMainProcessCommonConfig(root) {
return {
entryPoints: [path.join(root, 'main.ts')],
bundle: true,
sourcemap: true,
sourcesContent: false,
platform: 'node',
target: 'node16',
external: ['knex', 'electron', 'better-sqlite3'],
plugins: [excludeVendorFromSourceMap],
write: true,
};
}
/**
* ESBuild plugin used to prevent source maps from being generated for
* packages inside node_modules, only first-party code source maps
* are to be included.
*
* Note, this is used only for the main process source code.
*
* source: https://github.com/evanw/esbuild/issues/1685#issuecomment-944916409
* @type {import('esbuild').Plugin}
*/
export const excludeVendorFromSourceMap = {
name: 'excludeVendorFromSourceMap',
setup(build) {
build.onLoad({ filter: /node_modules/ }, (args) => {
if (args.path.endsWith('.json')) {
return;
}
return {
contents:
fs.readFileSync(args.path, 'utf8') +
'\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==',
loader: 'default',
};
});
},
};

View File

@ -1,23 +0,0 @@
import fs from 'fs';
/**
* source: https://github.com/evanw/esbuild/issues/1685#issuecomment-944916409
* @type {import('esbuild').Plugin}
*/
export const excludeVendorFromSourceMap = {
name: 'excludeVendorFromSourceMap',
setup(build) {
build.onLoad({ filter: /node_modules/ }, (args) => {
if (args.path.endsWith('.json')) {
return;
}
return {
contents:
fs.readFileSync(args.path, 'utf8') +
'\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==',
loader: 'default',
};
});
},
};

61
main.ts
View File

@ -8,9 +8,12 @@ import {
BrowserWindow, BrowserWindow,
BrowserWindowConstructorOptions, BrowserWindowConstructorOptions,
protocol, protocol,
ProtocolRequest,
ProtocolResponse,
} from 'electron'; } from 'electron';
import Store from 'electron-store'; import Store from 'electron-store';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import fs from 'fs';
import path from 'path'; import path from 'path';
import registerAppLifecycleListeners from './main/registerAppLifecycleListeners'; import registerAppLifecycleListeners from './main/registerAppLifecycleListeners';
import registerAutoUpdaterListeners from './main/registerAutoUpdaterListeners'; import registerAutoUpdaterListeners from './main/registerAutoUpdaterListeners';
@ -122,15 +125,20 @@ export class Main {
this.mainWindow = new BrowserWindow(options); this.mainWindow = new BrowserWindow(options);
if (this.isDevelopment) { if (this.isDevelopment) {
this.loadDevServerURL(); this.setViteServerURL();
} else { } else {
this.loadAppUrl(); this.registerAppProtocol();
}
this.mainWindow!.loadURL(this.winURL);
if (this.isDevelopment && !this.isTest) {
this.mainWindow!.webContents.openDevTools();
} }
this.setMainWindowListeners(); this.setMainWindowListeners();
} }
loadDevServerURL() { setViteServerURL() {
let port = 6969; let port = 6969;
let host = '0.0.0.0'; let host = '0.0.0.0';
@ -141,18 +149,13 @@ export class Main {
// Load the url of the dev server if in development mode // Load the url of the dev server if in development mode
this.winURL = `http://${host}:${port}/`; this.winURL = `http://${host}:${port}/`;
this.mainWindow!.loadURL(this.winURL);
if (this.isDevelopment && !this.isTest) {
this.mainWindow!.webContents.openDevTools();
}
} }
loadAppUrl() { registerAppProtocol() {
// createProtocol('app'); protocol.registerBufferProtocol('app', bufferProtocolCallback);
// Load the index.html when not in development
// this.winURL = 'app://./index.html'; // Use the registered protocol url to load the files.
// this.mainWindow!.loadURL(this.winURL); this.winURL = 'app://./index.html';
} }
setMainWindowListeners() { setMainWindowListeners() {
@ -170,4 +173,36 @@ export class Main {
} }
} }
/**
* Callback used to register the custom app protocol,
* during prod, files are read and served by using this
* protocol.
*/
function bufferProtocolCallback(
request: ProtocolRequest,
callback: (response: ProtocolResponse) => void
) {
const { pathname, host } = new URL(request.url);
const filePath = path.join(
__dirname,
'src',
decodeURI(host),
decodeURI(pathname)
);
fs.readFile(filePath, (_, data) => {
const extension = path.extname(filePath).toLowerCase();
const mimeType =
{
'.js': 'text/javascript',
'.css': 'text/css',
'.html': 'text/html',
'.svg': 'image/svg+xml',
'.json': 'application/json',
}[extension] ?? '';
callback({ mimeType, data });
});
}
export default new Main(); export default new Main();

View File

@ -29,7 +29,7 @@ async function getPrintTemplatePaths(): Promise<{
const files = await fs.readdir(root); const files = await fs.readdir(root);
return { files, root }; return { files, root };
} catch { } catch {
root = path.join(__dirname, `../templates`); root = path.join(__dirname, '..', '..', `templates`);
} }
try { try {

View File

@ -8,15 +8,13 @@
}, },
"scripts": { "scripts": {
"dev": "node build/scripts/dev.mjs", "dev": "node build/scripts/dev.mjs",
"build": "node build/scripts/build.mjs",
"dev:main": "node build/scripts/dev.mjs --no-renderer", "dev:main": "node build/scripts/dev.mjs --no-renderer",
"dev:renderer": "yarn vite", "dev:renderer": "yarn vite",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"release": "scripts/publish-mac-arm.sh", "release": "scripts/publish-mac-arm.sh",
"postinstall": "electron-rebuild", "postinstall": "electron-rebuild",
"postuninstall": "electron-rebuild", "postuninstall": "electron-rebuild",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"script:translate": "scripts/runner.sh scripts/generateTranslations.ts", "script:translate": "scripts/runner.sh scripts/generateTranslations.ts",
"script:profile": "scripts/profile.sh", "script:profile": "scripts/profile.sh",
"test": "scripts/test.sh" "test": "scripts/test.sh"
@ -73,6 +71,7 @@
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"execa": "^7.1.1", "execa": "^7.1.1",
"fs-extra": "^11.1.1",
"lint-staged": "^11.2.6", "lint-staged": "^11.2.6",
"postcss": "^8", "postcss": "^8",
"prettier": "^2.4.1", "prettier": "^2.4.1",

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Frappe Books</title>
</head>
<body>
<noscript>
<strong>We're sorry but Frappe Books doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- built files will replace body.innerHTML -->
</body>
</html>

View File

@ -2,11 +2,15 @@ import vue from '@vitejs/plugin-vue';
import path from 'path'; import path from 'path';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
/**
* This is a work in progress vite config. Currently only dev works.
*/
// https://vitejs.dev/config/ /**
* This vite config file is used only for dev mode, i.e.
* to create a serve build modules of the source code
* which will be rendered by electron.
*
* For building the project, vite is used programmatically
* see build/scripts/build.mjs for this.
*/
export default () => { export default () => {
let port = 6969; let port = 6969;
let host = '0.0.0.0'; let host = '0.0.0.0';

View File

@ -6534,6 +6534,15 @@ fs-extra@^10.1.0:
jsonfile "^6.0.1" jsonfile "^6.0.1"
universalify "^2.0.0" universalify "^2.0.0"
fs-extra@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^7.0.1: fs-extra@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"