Desktop: Performance: Faster startup and smaller application size (#12366)
This commit is contained in:
parent
a527a278a9
commit
86ee95a8d0
@ -578,6 +578,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/bundleJs.js
|
||||
packages/app-desktop/tools/copy7Zip.js
|
||||
packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
@ -592,6 +593,7 @@ packages/app-desktop/utils/customProtocols/constants.js
|
||||
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
|
||||
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
|
||||
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
|
||||
packages/app-desktop/utils/getAssetPath.js
|
||||
packages/app-desktop/utils/initializeCommandService.js
|
||||
packages/app-desktop/utils/isSafeToOpen.test.js
|
||||
packages/app-desktop/utils/isSafeToOpen.js
|
||||
|
@ -23,6 +23,7 @@ module.exports = {
|
||||
'FileSystemCreateWritableOptions': 'readonly',
|
||||
'FileSystemHandle': 'readonly',
|
||||
'IDBTransactionMode': 'readonly',
|
||||
'globalThis': 'readonly',
|
||||
|
||||
// ServiceWorker
|
||||
'ExtendableEvent': 'readonly',
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -46,6 +46,7 @@ sync_staging.sh
|
||||
TODO.md
|
||||
packages/tools/commit_hook.txt
|
||||
packages/tools/github_oauth_token.txt
|
||||
packages/app-desktop/main-html-out.js
|
||||
lerna-debug.log
|
||||
.env
|
||||
docs/**/*.mustache
|
||||
@ -552,6 +553,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/bundleJs.js
|
||||
packages/app-desktop/tools/copy7Zip.js
|
||||
packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
@ -566,6 +568,7 @@ packages/app-desktop/utils/customProtocols/constants.js
|
||||
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
|
||||
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
|
||||
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
|
||||
packages/app-desktop/utils/getAssetPath.js
|
||||
packages/app-desktop/utils/initializeCommandService.js
|
||||
packages/app-desktop/utils/isSafeToOpen.test.js
|
||||
packages/app-desktop/utils/isSafeToOpen.js
|
||||
|
@ -1,6 +1,22 @@
|
||||
|
||||
# We remove the `canvas` optional dependency because electron-rebuild fails to build it, and
|
||||
# the `canvas` API is already part of Electron
|
||||
diff --git a/build/pdf.js b/build/pdf.js
|
||||
index 4acf16b1d6f9351bda1a98649ea4f926618fe617..f63dbc6050ca63ca8e8ed982edea134103fa15dd 100644
|
||||
--- a/build/pdf.js
|
||||
+++ b/build/pdf.js
|
||||
@@ -6244,8 +6244,9 @@ class NodeFilterFactory extends _base_factory.BaseFilterFactory {}
|
||||
exports.NodeFilterFactory = NodeFilterFactory;
|
||||
class NodeCanvasFactory extends _base_factory.BaseCanvasFactory {
|
||||
_createCanvas(width, height) {
|
||||
- const Canvas = require("canvas");
|
||||
- return Canvas.createCanvas(width, height);
|
||||
+ throw new Error('Node canvas disabled');
|
||||
+ // const Canvas = require("canvas");
|
||||
+ // return Canvas.createCanvas(width, height);
|
||||
}
|
||||
}
|
||||
exports.NodeCanvasFactory = NodeCanvasFactory;
|
||||
diff --git a/package.json b/package.json
|
||||
index 105811f53d508486e08a60dc1b6e437cd24d7427..dea6a4e6612c4a4006cc482e46ff5270dcfda1e5 100644
|
||||
--- a/package.json
|
||||
|
@ -15,9 +15,9 @@
|
||||
"scripts": {
|
||||
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
|
||||
"buildScriptIndexes": "node packages/tools/gulp/tasks/buildScriptIndexesRun.js",
|
||||
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn tsc",
|
||||
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological-dev run build && yarn tsc",
|
||||
"buildPluginDoc": "cd packages/generate-plugin-doc && yarn buildPluginDoc_",
|
||||
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn tsc",
|
||||
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological-dev run build && yarn tsc",
|
||||
"buildServerDocker": "node packages/tools/buildServerDocker.js",
|
||||
"buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json",
|
||||
"buildTranslations": "node packages/tools/build-translation.js",
|
||||
@ -110,6 +110,9 @@
|
||||
"app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch",
|
||||
"react-native-sqlite-storage@6.0.1": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch",
|
||||
"react-native-paper@5.13.1": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch",
|
||||
"react-native-popup-menu@0.17.0": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch"
|
||||
"react-native-popup-menu@0.17.0": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch",
|
||||
"pdfjs-dist@2.16.105": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import Note from '@joplin/lib/models/Note';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { reg } from '@joplin/lib/registry.js';
|
||||
import { fileExtension } from '@joplin/lib/path-utils';
|
||||
import { dirname, fileExtension } from '@joplin/lib/path-utils';
|
||||
import { splitCommandString } from '@joplin/utils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { pathExists, readFile, readdirSync } from 'fs-extra';
|
||||
@ -397,8 +397,12 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
public async start(argv: string[]) {
|
||||
const keychainEnabled = this.checkIfKeychainEnabled(argv);
|
||||
// TODO: Currently, `pluginAssetDir` needs to be set differently for each platform and requires
|
||||
// a call to Setting.setConstant. Ideally, this would be done in a way that requires users to
|
||||
// set this constant on startup.
|
||||
Setting.setConstant('pluginAssetDir', `${dirname(require.resolve('@joplin/renderer'))}/assets`);
|
||||
|
||||
const keychainEnabled = this.checkIfKeychainEnabled(argv);
|
||||
argv = await super.start(argv, { keychainEnabled });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
|
4
packages/app-desktop/.gitignore
vendored
4
packages/app-desktop/.gitignore
vendored
@ -25,3 +25,7 @@ build/7zip/7za
|
||||
build/7zip/7za.exe
|
||||
sentry.properties
|
||||
downloads/
|
||||
|
||||
# Bundler output
|
||||
*.js.meta.json
|
||||
*.bundle.js
|
||||
|
@ -246,7 +246,7 @@ export class Bridge {
|
||||
// version of electron-context-menu.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public setupContextMenu(_spellCheckerMenuItemsHandler: Function) {
|
||||
require('electron-context-menu')({
|
||||
require('./services/electron-context-menu')({
|
||||
allWindows: [this.mainWindow()],
|
||||
|
||||
electronApp: this.electronApp(),
|
||||
|
@ -3,7 +3,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'startExternalEditing',
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import ButtonBar from './ConfigScreen/ButtonBar';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const Shared = require('@joplin/lib/components/shared/dropbox-login-shared');
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
||||
import EmojiBox from './EmojiBox';
|
||||
|
||||
|
@ -4,7 +4,6 @@ import ButtonBar from './ConfigScreen/ButtonBar';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { clipboard } from 'electron';
|
||||
import Button, { ButtonLevel } from './Button/Button';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import { uuidgen } from '@joplin/lib/uuid';
|
||||
import { Dispatch } from 'redux';
|
||||
import { reducer, defaultState, generateApplicationConfirmUrl, checkIfLoginWasSuccessful } from '@joplin/lib/services/joplinCloudUtils';
|
||||
@ -12,6 +11,7 @@ import { AppState } from '../app.reducer';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import JoplinCloudSignUpCallToAction from './JoplinCloudSignUpCallToAction';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
const logger = Logger.create('JoplinCloudLoginScreen');
|
||||
const { connect } = require('react-redux');
|
||||
@ -62,7 +62,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
|
||||
|
||||
const onAuthorizeClicked = async () => {
|
||||
const url = await generateApplicationConfirmUrl(confirmUrl(applicationAuthId));
|
||||
bridge().openExternal(url);
|
||||
void bridge().openExternal(url);
|
||||
onButtonUsed();
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
|
||||
import * as CodeMirror from 'codemirror';
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import 'codemirror/addon/comment/comment';
|
||||
import 'codemirror/addon/dialog/dialog';
|
||||
@ -32,54 +32,41 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
// import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
|
||||
// Based on http://pypl.github.io/PYPL.html
|
||||
const topLanguages = [
|
||||
'python',
|
||||
'clike',
|
||||
'javascript',
|
||||
'jsx',
|
||||
'php',
|
||||
'r',
|
||||
'swift',
|
||||
'go',
|
||||
'vb',
|
||||
'vbscript',
|
||||
'ruby',
|
||||
'rust',
|
||||
'dart',
|
||||
'lua',
|
||||
'groovy',
|
||||
'perl',
|
||||
'cobol',
|
||||
'julia',
|
||||
'haskell',
|
||||
'pascal',
|
||||
'css',
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/clike/clike';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/jsx/jsx';
|
||||
import 'codemirror/mode/php/php';
|
||||
import 'codemirror/mode/r/r';
|
||||
import 'codemirror/mode/swift/swift';
|
||||
import 'codemirror/mode/go/go';
|
||||
import 'codemirror/mode/vb/vb';
|
||||
import 'codemirror/mode/vbscript/vbscript';
|
||||
import 'codemirror/mode/ruby/ruby';
|
||||
import 'codemirror/mode/rust/rust';
|
||||
import 'codemirror/mode/dart/dart';
|
||||
import 'codemirror/mode/lua/lua';
|
||||
import 'codemirror/mode/groovy/groovy';
|
||||
import 'codemirror/mode/perl/perl';
|
||||
import 'codemirror/mode/cobol/cobol';
|
||||
import 'codemirror/mode/julia/julia';
|
||||
import 'codemirror/mode/haskell/haskell';
|
||||
import 'codemirror/mode/pascal/pascal';
|
||||
import 'codemirror/mode/css/css';
|
||||
|
||||
// Additional languages, not in the PYPL list
|
||||
'xml', // For HTML too
|
||||
'markdown',
|
||||
'yaml',
|
||||
'shell',
|
||||
'dockerfile',
|
||||
'diff',
|
||||
'erlang',
|
||||
'sql',
|
||||
];
|
||||
// Load Top Modes
|
||||
for (let i = 0; i < topLanguages.length; i++) {
|
||||
const mode = topLanguages[i];
|
||||
// Additional languages, not in the PYPL lis;
|
||||
import 'codemirror/mode/xml/xml'; // For HTML too
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/mode/yaml/yaml';
|
||||
import 'codemirror/mode/shell/shell';
|
||||
import 'codemirror/mode/dockerfile/dockerfile';
|
||||
import 'codemirror/mode/diff/diff';
|
||||
import 'codemirror/mode/erlang/erlang';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
if (CodeMirror.modeInfo.find((m: any) => m.mode === mode)) {
|
||||
require(`codemirror/mode/${mode}/${mode}`);
|
||||
} else {
|
||||
reg.logger().error('Cannot find CodeMirror mode: ', mode);
|
||||
}
|
||||
}
|
||||
|
||||
export interface EditorProps {
|
||||
value: string;
|
||||
|
@ -2,7 +2,6 @@ import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||
import htmlUtils from '@joplin/lib/htmlUtils';
|
||||
import rendererHtmlUtils, { extractHtmlBody, removeWrappingParagraphAndTrailingEmptyElements } from '@joplin/renderer/htmlUtils';
|
||||
@ -15,6 +14,7 @@ import { fileExtension, filename, safeFileExtension, safeFilename } from '@jopli
|
||||
const joplinRendererUtils = require('@joplin/renderer').utils;
|
||||
const { clipboard } = require('electron');
|
||||
import * as mimeUtils from '@joplin/lib/mime-utils';
|
||||
import bridge from '../../../services/bridge';
|
||||
const md5 = require('md5');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -7,6 +7,8 @@ import { ForwardedRef, forwardRef, RefObject, useContext, useEffect, useImperati
|
||||
import { WindowIdContext } from './NewWindowOrIFrame';
|
||||
import useDocument from './hooks/useDocument';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import getAssetPath from '../utils/getAssetPath';
|
||||
import { toForwardSlashes } from '@joplin/utils/path';
|
||||
|
||||
interface Props {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
@ -240,7 +242,7 @@ const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerCon
|
||||
allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)'
|
||||
allowFullScreen={true}
|
||||
aria-label={_('Note viewer')}
|
||||
src={`joplin-content://note-viewer/${__dirname}/note-viewer/index.html`}
|
||||
src={`joplin-content://note-viewer/${toForwardSlashes(getAssetPath('gui/note-viewer/index.html'))}`}
|
||||
></iframe>
|
||||
);
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
const { connect } = require('react-redux');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import bridge from '../services/bridge';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
|
||||
|
||||
@ -66,7 +66,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||
const logComps = [];
|
||||
for (const l of this.state.authLog) {
|
||||
if (l.text.indexOf('http:') === 0) {
|
||||
logComps.push(<a key={l.key} style={theme.urlStyle} href="#" onClick={() => { bridge().openExternal(l.text); }}>{l.text}</a>);
|
||||
logComps.push(<a key={l.key} style={theme.urlStyle} href="#" onClick={() => { void bridge().openExternal(l.text); }}>{l.text}</a>);
|
||||
} else {
|
||||
logComps.push(<p key={l.key} style={theme.textStyle}>{l.text}</p>);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import MoveButtons, { MoveButtonClickEvent } from './MoveButtons';
|
||||
import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootMessage } from './utils/style';
|
||||
import type { ResizeCallback, ResizeStartCallback } from 're-resizable';
|
||||
import Dialog from '../Dialog';
|
||||
import * as EventEmitter from 'events';
|
||||
import EventEmitter = require('events');
|
||||
import LayoutItemContainer from './LayoutItemContainer';
|
||||
|
||||
interface OnResizeEvent {
|
||||
|
@ -343,4 +343,4 @@ const mapStateToProps = (state: any) => ({
|
||||
|
||||
const ResourceScreen = connect(mapStateToProps)(ResourceScreenComponent);
|
||||
|
||||
module.exports = { ResourceScreen };
|
||||
export default ResourceScreen;
|
||||
|
@ -21,7 +21,7 @@ import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './Di
|
||||
import Dialog from './Dialog';
|
||||
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
|
||||
import ImportScreen from './ImportScreen';
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
import ResourceScreen from './ResourceScreen';
|
||||
import Navigator from './Navigator';
|
||||
import WelcomeUtils from '@joplin/lib/WelcomeUtils';
|
||||
import JoplinCloudLoginScreen from './JoplinCloudLoginScreen';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { WindowControl } from '../utils/useWindowControl';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'print',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'renameFolder',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'renameTag',
|
||||
|
@ -4,9 +4,18 @@ const compileSass = require('@joplin/tools/compileSass');
|
||||
const compilePackageInfo = require('@joplin/tools/compilePackageInfo');
|
||||
import buildDefaultPlugins from '@joplin/default-plugins/commands/buildAll';
|
||||
import copy7Zip from './tools/copy7Zip';
|
||||
import bundleJs from './tools/bundleJs';
|
||||
import { remove } from 'fs-extra';
|
||||
|
||||
const tasks = {
|
||||
bundle: {
|
||||
fn: () => bundleJs(false),
|
||||
},
|
||||
// Bundles and computes additional information that can be analysed with
|
||||
// locally or with https://esbuild.github.io/analyze/.
|
||||
bundleWithStats: {
|
||||
fn: () => bundleJs(true),
|
||||
},
|
||||
compileScripts: {
|
||||
fn: require('./tools/compileScripts'),
|
||||
},
|
||||
@ -54,7 +63,7 @@ const tasks = {
|
||||
|
||||
utils.registerGulpTasks(gulp, tasks);
|
||||
|
||||
const buildBeforeStartParallel = [
|
||||
const buildBeforeStartParallel = gulp.parallel(
|
||||
'compileScripts',
|
||||
'compilePackageInfo',
|
||||
'copyPluginAssets',
|
||||
@ -62,14 +71,21 @@ const buildBeforeStartParallel = [
|
||||
'updateIgnoredTypeScriptBuild',
|
||||
'buildScriptIndexes',
|
||||
'compileSass',
|
||||
];
|
||||
);
|
||||
const buildRequiresTsc = gulp.series('bundle');
|
||||
|
||||
gulp.task('before-start', gulp.parallel(...buildBeforeStartParallel));
|
||||
gulp.task('before-start', gulp.series(
|
||||
buildRequiresTsc,
|
||||
buildBeforeStartParallel,
|
||||
));
|
||||
gulp.task('before-dist', buildRequiresTsc);
|
||||
|
||||
const buildAllSequential = [
|
||||
'before-start',
|
||||
// Since "build" runs before "tsc", exclude tasks that require
|
||||
// other packages to be built (i.e. don't include buildRequiresTsc).
|
||||
const buildSequential = [
|
||||
buildBeforeStartParallel,
|
||||
'copyDefaultPluginsAssets',
|
||||
'buildDefaultPlugins',
|
||||
];
|
||||
|
||||
gulp.task('build', gulp.series(buildAllSequential));
|
||||
gulp.task('build', gulp.series(buildSequential));
|
||||
|
@ -12,11 +12,11 @@
|
||||
<link rel="stylesheet" href="style.min.css">
|
||||
|
||||
<script src="vendor/lib/smalltalk/dist/smalltalk.min.js"></script>
|
||||
<script src="./node_modules/tesseract.js/dist/tesseract.min.js"></script>
|
||||
<script src="vendor/lib/tesseract.js/dist/tesseract.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-root"></div>
|
||||
<script src="./utils/window/eventHandlerOverrides.js"></script>
|
||||
<script src="main-html.js"></script>
|
||||
<script src="./main-html.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { ElectronApplication, expect, Locator, Page } from '@playwright/test';
|
||||
import MainScreen from './MainScreen';
|
||||
import activateMainMenuItem from '../util/activateMainMenuItem';
|
||||
import { msleep } from '@joplin/utils/time';
|
||||
|
||||
export default class GoToAnything {
|
||||
public readonly containerLocator: Locator;
|
||||
@ -38,6 +39,8 @@ export default class GoToAnything {
|
||||
// This expect.poll retries the search if it initially fails.
|
||||
await expect.poll(async () => {
|
||||
await this.inputLocator.clear();
|
||||
// Pause to help ensure that the change in the search input is detected
|
||||
await msleep(300);
|
||||
await this.inputLocator.fill(query);
|
||||
try {
|
||||
await expect(resultLocator).toBeVisible({ timeout: 1000 });
|
||||
@ -47,7 +50,7 @@ export default class GoToAnything {
|
||||
return error;
|
||||
}
|
||||
return true;
|
||||
}, { timeout: 10_000 }).toBe(true);
|
||||
}, { timeout: 15_000 }).toBe(true);
|
||||
}
|
||||
|
||||
public async expectToBeClosed() {
|
||||
|
@ -3,7 +3,7 @@ import { dirname, resolve } from 'path';
|
||||
const createStartupArgs = (profileDirectory: string) => {
|
||||
// Input paths need to be absolute when running from VSCode
|
||||
const baseDirectory = dirname(dirname(__dirname));
|
||||
const mainPath = resolve(baseDirectory, 'main.js');
|
||||
const mainPath = resolve(baseDirectory, 'main.bundle.js');
|
||||
|
||||
// We need to run with --env dev to disable the single instance check.
|
||||
return [
|
||||
|
@ -19,6 +19,7 @@ jest.mock('@electron/remote', () => {
|
||||
default: {},
|
||||
};
|
||||
},
|
||||
getGlobal: () => ({}),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Disable React message in console "Download the React DevTools for a better development experience"
|
||||
// https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820
|
||||
// eslint-disable-next-line no-undef
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
supportsFiber: true,
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
@ -22,9 +22,9 @@ const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Revision = require('@joplin/lib/models/Revision').default;
|
||||
const Logger = require('@joplin/utils/Logger').default;
|
||||
const FsDriverNode = require('@joplin/lib/fs-driver-node').default;
|
||||
const bridge = require('./services/bridge').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService').default;
|
||||
const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default;
|
||||
const React = require('react');
|
||||
@ -34,6 +34,9 @@ const pdfJs = require('pdfjs-dist');
|
||||
const { isAppleSilicon } = require('is-apple-silicon');
|
||||
require('@sentry/electron/renderer');
|
||||
|
||||
// Allows components to use React as a global
|
||||
window.React = React;
|
||||
|
||||
|
||||
const main = async () => {
|
||||
if (bridge().env() === 'dev') {
|
||||
@ -83,6 +86,7 @@ const main = async () => {
|
||||
|
||||
Setting.setConstant('appId', bridge().appId());
|
||||
Setting.setConstant('appType', 'desktop');
|
||||
Setting.setConstant('pluginAssetDir', `${__dirname}/pluginAssets`);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`appId: ${Setting.value('appId')}`);
|
||||
|
@ -11,7 +11,7 @@ const envFromArgs = require('@joplin/lib/envFromArgs');
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
const { isCallbackUrl } = require('@joplin/lib/callbackUrlUtils');
|
||||
const determineBaseAppDirs = require('@joplin/lib/determineBaseAppDirs').default;
|
||||
const registerCustomProtocols = require('./utils/customProtocols/registerCustomProtocols.js').default;
|
||||
const registerCustomProtocols = require('./utils/customProtocols/registerCustomProtocols').default;
|
||||
|
||||
// Electron takes the application name from package.json `name` and
|
||||
// displays this in the tray icon toolip and message box titles, however in
|
||||
@ -74,7 +74,7 @@ const wrapper = new ElectronAppWrapper(electronApp, {
|
||||
env, profilePath: rootProfileDir, isDebugMode, initialCallbackUrl, isEndToEndTesting,
|
||||
});
|
||||
|
||||
initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId);
|
||||
globalThis.joplinBridge = initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId);
|
||||
|
||||
wrapper.start().catch((error) => {
|
||||
console.error('Electron App fatal error:');
|
||||
|
@ -2,18 +2,19 @@
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "3.4.1",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"main": "main.bundle.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dist": "yarn electronRebuild && npx electron-builder",
|
||||
"dist": "gulp before-dist && yarn electronRebuild && npx electron-builder",
|
||||
"build": "gulp build",
|
||||
"bundle": "gulp bundleWithStats",
|
||||
"electronBuilder": "gulp electronBuilder",
|
||||
"electronRebuild": "gulp electronRebuild",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
|
||||
"start": "gulp before-start && electron . --env dev --log-level debug --open-dev-tools --no-welcome",
|
||||
"test": "jest",
|
||||
"test-ui": "playwright test",
|
||||
"test-ui": "gulp before-start && playwright test",
|
||||
"test-ci": "yarn test",
|
||||
"modifyReleaseAssets": "node tools/modifyReleaseAssets.js"
|
||||
},
|
||||
@ -131,61 +132,53 @@
|
||||
"devDependencies": {
|
||||
"7zip-bin": "5.2.0",
|
||||
"@axe-core/playwright": "4.10.1",
|
||||
"@electron/notarize": "2.3.2",
|
||||
"@electron/rebuild": "3.6.0",
|
||||
"@fortawesome/fontawesome-free": "5.15.4",
|
||||
"@joeattardi/emoji-button": "4.6.4",
|
||||
"@joplin/default-plugins": "~3.4",
|
||||
"@joplin/editor": "~3.4",
|
||||
"@joplin/lib": "~3.4",
|
||||
"@joplin/renderer": "~3.4",
|
||||
"@joplin/tools": "~3.4",
|
||||
"@joplin/utils": "~3.4",
|
||||
"@playwright/test": "1.51.1",
|
||||
"@sentry/electron": "4.24.0",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/mustache": "4.2.5",
|
||||
"@types/node": "18.19.67",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/react-redux": "7.1.33",
|
||||
"@types/styled-components": "5.1.32",
|
||||
"@types/tesseract.js": "2.0.0",
|
||||
"axios": "^1.7.7",
|
||||
"electron": "35.2.1",
|
||||
"electron-builder": "24.13.3",
|
||||
"glob": "10.4.5",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"js-sha512": "0.9.0",
|
||||
"nan": "2.19.0",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"ts-jest": "29.1.5",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/notarize": "2.3.2",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@fortawesome/fontawesome-free": "5.15.4",
|
||||
"@joeattardi/emoji-button": "4.6.4",
|
||||
"@joplin/editor": "~3.4",
|
||||
"@joplin/lib": "~3.4",
|
||||
"@joplin/renderer": "~3.4",
|
||||
"@joplin/utils": "~3.4",
|
||||
"@sentry/electron": "4.24.0",
|
||||
"@types/mustache": "4.2.5",
|
||||
"async-mutex": "0.5.0",
|
||||
"axios": "^1.7.7",
|
||||
"codemirror": "5.65.9",
|
||||
"color": "3.2.1",
|
||||
"compare-versions": "6.1.1",
|
||||
"countable": "3.0.1",
|
||||
"debounce": "1.2.1",
|
||||
"electron": "35.2.1",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-updater": "6.2.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"esbuild": "^0.25.3",
|
||||
"formatcoords": "1.1.3",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "10.4.5",
|
||||
"gulp": "4.0.2",
|
||||
"highlight.js": "11.10.0",
|
||||
"immer": "9.0.21",
|
||||
"is-apple-silicon": "1.1.2",
|
||||
"keytar": "7.9.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"js-sha512": "0.9.0",
|
||||
"mark.js": "8.11.1",
|
||||
"md5": "2.3.0",
|
||||
"moment": "2.30.1",
|
||||
"mustache": "4.2.0",
|
||||
"nan": "2.19.0",
|
||||
"node-fetch": "2.6.7",
|
||||
"node-notifier": "10.0.1",
|
||||
"node-rsa": "1.1.1",
|
||||
@ -196,17 +189,27 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-redux": "8.1.3",
|
||||
"react-select": "5.8.0",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"react-toggle-button": "2.2.0",
|
||||
"react-tooltip": "4.5.1",
|
||||
"redux": "4.2.1",
|
||||
"reselect": "4.1.8",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"smalltalk": "2.5.1",
|
||||
"sqlite3": "5.1.6",
|
||||
"styled-components": "5.3.11",
|
||||
"styled-system": "5.1.5",
|
||||
"taboverride": "4.0.3",
|
||||
"tesseract.js": "5.1.0",
|
||||
"tinymce": "6.8.5"
|
||||
"tinymce": "6.8.5",
|
||||
"ts-jest": "29.1.5",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/remote": "2.1.2",
|
||||
"@joplin/onenote-converter": "~3.4",
|
||||
"fs-extra": "11.2.0",
|
||||
"keytar": "7.9.0",
|
||||
"sqlite3": "5.1.6"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
// Just a convenient wrapper to get a typed bridge in TypeScript
|
||||
|
||||
import { Bridge } from '../bridge';
|
||||
import type { Bridge } from '../bridge';
|
||||
|
||||
const remoteBridge = require('@electron/remote').require('./bridge').default;
|
||||
const remoteBridge = require('@electron/remote').getGlobal('joplinBridge');
|
||||
|
||||
export default function bridge(): Bridge {
|
||||
return remoteBridge();
|
||||
return remoteBridge;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandle
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import getPathToExecutable7Zip from '../../utils/7zip/getPathToExecutable7Zip';
|
||||
import getAssetPath from '../../utils/getAssetPath';
|
||||
// import BackOffHandler from './BackOffHandler';
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
@ -134,7 +135,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
};
|
||||
|
||||
void pluginWindow.loadURL(`${require('url').format({
|
||||
pathname: require('path').join(__dirname, 'plugin_index.html'),
|
||||
pathname: getAssetPath('services/plugins/plugin_index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
})}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}&libraryData=${encodeURIComponent(JSON.stringify(libraryData))}`);
|
||||
|
@ -12,6 +12,8 @@ import { WindowIdContext } from '../../gui/NewWindowOrIFrame';
|
||||
import useSubmitHandler from './hooks/useSubmitHandler';
|
||||
import useFormData from './hooks/useFormData';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import getAssetPath from '../../utils/getAssetPath';
|
||||
import { toForwardSlashes } from '@joplin/utils/path';
|
||||
|
||||
const logger = Logger.create('UserWebview');
|
||||
|
||||
@ -124,7 +126,7 @@ function UserWebview(props: Props, ref: any) {
|
||||
|
||||
const src = useMemo(() => {
|
||||
const isolate = Setting.value('featureFlag.plugins.isolatePluginWebViews');
|
||||
const path = `${__dirname}/UserWebviewIndex.html`;
|
||||
const path = toForwardSlashes(getAssetPath('services/plugins/UserWebviewIndex.html'));
|
||||
if (isolate) {
|
||||
return `joplin-content://plugin-webview/${path}`;
|
||||
} else {
|
||||
|
111
packages/app-desktop/tools/bundleJs.ts
Normal file
111
packages/app-desktop/tools/bundleJs.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { filename, toForwardSlashes } from '@joplin/utils/path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { existsSync } from 'fs';
|
||||
import { writeFile } from 'fs/promises';
|
||||
import { dirname, join, relative } from 'path';
|
||||
|
||||
// Note: Roughly based on js-draw's use of esbuild:
|
||||
// https://github.com/personalizedrefrigerator/js-draw/blob/6fe6d6821402a08a8d17f15a8f48d95e5d7b084f/packages/build-tool/src/BundledFile.ts#L64
|
||||
const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSizeStats: boolean) => {
|
||||
return esbuild.context({
|
||||
entryPoints: [entryPoint],
|
||||
outfile: `${filename(entryPoint)}.bundle.js`,
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
metafile: computeFileSizeStats,
|
||||
platform: 'node',
|
||||
target: ['node20.0'],
|
||||
mainFields: renderer ? ['browser', 'main'] : ['main'],
|
||||
plugins: [
|
||||
{
|
||||
// Configures ESBuild to require(...) certain libraries that cause issues if included directly
|
||||
// in the bundle. Some of these are transitive dependencies and so need to have relative paths
|
||||
// in the final bundle.
|
||||
name: 'joplin--relative-imports-for-externals',
|
||||
setup: build => {
|
||||
const externalRegex = /^(.*\.node|sqlite3|electron|@electron\/remote\/.*|electron\/.*|@mapbox\/node-pre-gyp|jsdom)$/;
|
||||
const baseDir = dirname(__dirname);
|
||||
const baseNodeModules = join(baseDir, 'node_modules');
|
||||
build.onResolve({ filter: externalRegex }, args => {
|
||||
// Electron packages don't need relative requires
|
||||
if (args.path === 'electron' || args.path.startsWith('electron/')) {
|
||||
return { path: args.path, external: true, namespace: 'node' };
|
||||
}
|
||||
|
||||
// Other packages may need relative requires
|
||||
let path = toForwardSlashes(relative(
|
||||
baseDir,
|
||||
require.resolve(args.path, { paths: [baseNodeModules, args.resolveDir, baseDir] }),
|
||||
));
|
||||
if (!path.startsWith('.')) {
|
||||
path = `./${path}`;
|
||||
}
|
||||
|
||||
// Some files have .node.* extensions but are not native modules. These files are often required using
|
||||
// require('./something.node') rather than require('./something.node.js'). Skip path remapping for
|
||||
// these files:
|
||||
if (args.path.endsWith('.node') && (path.endsWith('.ts') || path.endsWith('.js'))) {
|
||||
// Normal .ts or .js file -- continue.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Log that this is external -- it should be included in "dependencies" and not "devDependencies" in package.json:
|
||||
console.log('External path:', path, args.importer);
|
||||
return {
|
||||
path,
|
||||
external: true,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
// Rewrite imports to prefer .js files to .ts. Otherwise, certain files are duplicated in the final bundle
|
||||
name: 'joplin--prefer-js-imports',
|
||||
setup: build => {
|
||||
const baseDir = dirname(__dirname);
|
||||
const baseNodeModules = join(baseDir, 'node_modules');
|
||||
// Rewrite all relative imports
|
||||
build.onResolve({ filter: /^\./ }, args => {
|
||||
try {
|
||||
const importPath = args.path === '.' ? './index' : args.path;
|
||||
let path = require.resolve(importPath, { paths: [args.resolveDir, baseNodeModules, baseDir] });
|
||||
// require.resolve **can** return paths with .ts extensions, presumably because
|
||||
// this build script is a .ts file.
|
||||
if (path.endsWith('.ts')) {
|
||||
const alternative = path.replace(/\.ts$/, '.js');
|
||||
if (existsSync(alternative)) {
|
||||
path = alternative;
|
||||
}
|
||||
}
|
||||
return { path };
|
||||
} catch (error) {
|
||||
return {
|
||||
errors: [{ text: `Failed to import: ${error}`, detail: error }],
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const bundleJs = async (writeStats: boolean) => {
|
||||
const entryPoints = [
|
||||
{ fileName: 'main.js', renderer: false },
|
||||
{ fileName: 'main-html.js', renderer: true },
|
||||
];
|
||||
for (const { fileName, renderer } of entryPoints) {
|
||||
const compiler = await makeBuildContext(fileName, renderer, writeStats);
|
||||
const result = await compiler.rebuild();
|
||||
if (writeStats) {
|
||||
const outPath = `${dirname(__dirname)}/${fileName}.meta.json`;
|
||||
console.log('Writing bundle stats to ', outPath);
|
||||
await writeFile(outPath, JSON.stringify(result.metafile, undefined, '\t'));
|
||||
}
|
||||
await compiler.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
export default bundleJs;
|
@ -90,6 +90,7 @@ async function main() {
|
||||
'smalltalk/dist/smalltalk.min.js',
|
||||
'smalltalk/img/IDR_CLOSE_DIALOG_H.png',
|
||||
'smalltalk/img/IDR_CLOSE_DIALOG.png',
|
||||
'tesseract.js/dist/tesseract.min.js',
|
||||
{
|
||||
src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'),
|
||||
dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`,
|
||||
|
@ -11,5 +11,5 @@
|
||||
// Exclude gulpfile.ts to prevent Gulp from trying to build from
|
||||
// gulpfile.js.
|
||||
"gulpfile.ts"
|
||||
],
|
||||
]
|
||||
}
|
9
packages/app-desktop/utils/getAssetPath.ts
Normal file
9
packages/app-desktop/utils/getAssetPath.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const getAssetPath = (path: string) => {
|
||||
// __dirname sometimes points to app-desktop/
|
||||
const baseDir = __dirname.match(/utils[/\\]?$/) ? dirname(__dirname) : __dirname;
|
||||
return join(baseDir, path);
|
||||
};
|
||||
|
||||
export default getAssetPath;
|
@ -19,7 +19,7 @@ export default class PluginAssetsLoader {
|
||||
}
|
||||
|
||||
private destDir_() {
|
||||
return `${Setting.value('resourceDir')}/pluginAssets`;
|
||||
return Setting.value('pluginAssetDir');
|
||||
}
|
||||
|
||||
private async importAssetsMobile_() {
|
||||
|
@ -527,6 +527,7 @@ async function initialize(dispatch: Dispatch) {
|
||||
Setting.setConstant('cacheDir', `${getProfilesRootDir()}/cache`);
|
||||
const resourceDir = getResourceDir(currentProfile, isSubProfile);
|
||||
Setting.setConstant('resourceDir', resourceDir);
|
||||
Setting.setConstant('pluginAssetDir', `${Setting.value('resourceDir')}/pluginAssets`);
|
||||
Setting.setConstant('pluginDir', `${getProfilesRootDir()}/plugins`);
|
||||
Setting.setConstant('pluginDataDir', getPluginDataDir(currentProfile, isSubProfile));
|
||||
|
||||
|
@ -29,8 +29,9 @@
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./lib/uslug": {
|
||||
"types": "./lib/uslug.ts",
|
||||
"require": "./lib/uslug.js",
|
||||
"types": "./lib/uslug.ts"
|
||||
"default": "./lib/uslug.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
|
@ -6,12 +6,12 @@
|
||||
"types": "index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.js",
|
||||
"types": "./index.ts"
|
||||
"types": "./index.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./packToString": {
|
||||
"default": "./dist/packToString.js",
|
||||
"types": "./packToString.ts"
|
||||
"types": "./packToString.ts",
|
||||
"default": "./dist/packToString.js"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import shim from './shim';
|
||||
import { _ } from './locale';
|
||||
const { rtrimSlashes } = require('./path-utils.js');
|
||||
import { rtrimSlashes } from './path-utils';
|
||||
import JoplinError from './JoplinError';
|
||||
import { Env } from './models/Setting';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import FileApiDriverJoplinServer from './file-api-driver-joplinServer';
|
||||
import Setting from './models/Setting';
|
||||
import Synchronizer from './Synchronizer';
|
||||
import { _ } from './locale.js';
|
||||
import { _ } from './locale';
|
||||
import JoplinServerApi, { Session } from './JoplinServerApi';
|
||||
import BaseSyncTarget from './BaseSyncTarget';
|
||||
import { FileApi } from './file-api';
|
||||
|
@ -21,6 +21,16 @@ export interface RemoveOptions {
|
||||
recursive?: boolean;
|
||||
}
|
||||
|
||||
export interface ZipExtractOptions {
|
||||
source: string;
|
||||
extractTo: string;
|
||||
}
|
||||
|
||||
export interface ZipEntry {
|
||||
entryName: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
export default class FsDriverBase {
|
||||
|
||||
@ -258,4 +268,8 @@ export default class FsDriverBase {
|
||||
throw new Error('Not implemented: tarCreate');
|
||||
}
|
||||
|
||||
public async zipExtract(_options: ZipExtractOptions): Promise<ZipEntry[]> {
|
||||
throw new Error('Not implemented: zipExtract');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import FsDriverBase, { Stat } from './fs-driver-base';
|
||||
import AdmZip = require('adm-zip');
|
||||
import FsDriverBase, { Stat, ZipEntry, ZipExtractOptions } from './fs-driver-base';
|
||||
import time from './time';
|
||||
const md5File = require('md5-file');
|
||||
const fs = require('fs-extra');
|
||||
@ -210,4 +211,9 @@ export default class FsDriverNode extends FsDriverBase {
|
||||
await require('tar').create(options, filePaths);
|
||||
}
|
||||
|
||||
public async zipExtract(options: ZipExtractOptions): Promise<ZipEntry[]> {
|
||||
const zip = new AdmZip(options.source);
|
||||
zip.extractAllTo(options.extractTo, false);
|
||||
return zip.getEntries();
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,14 @@ const { wrapError } = require('./errorUtils');
|
||||
const { enexXmlToHtml } = require('./import-enex-html-gen.js');
|
||||
const md5 = require('md5');
|
||||
const { Base64Decode } = require('base64-stream');
|
||||
const md5File = require('md5-file');
|
||||
import * as mime from './mime-utils';
|
||||
import type * as FsExtra from 'fs-extra';
|
||||
|
||||
// const Promise = require('promise');
|
||||
const fs = require('fs-extra');
|
||||
let fs_: typeof FsExtra = null;
|
||||
const fs = () => {
|
||||
fs_ ??= shim.requireDynamic('fs-extra');
|
||||
return fs_;
|
||||
};
|
||||
|
||||
function dateToTimestamp(s: string, defaultValue: number = null): number {
|
||||
// Most dates seem to be in this format
|
||||
@ -60,9 +63,9 @@ async function decodeBase64File(sourceFilePath: string, destFilePath: string) {
|
||||
// to disk, thus resulting in the calling code to find a
|
||||
// file with size 0.
|
||||
|
||||
const destFile = fs.openSync(destFilePath, 'w');
|
||||
const sourceStream = fs.createReadStream(sourceFilePath);
|
||||
const destStream = fs.createWriteStream(destFile, {
|
||||
const destFile = fs().openSync(destFilePath, 'w');
|
||||
const sourceStream = fs().createReadStream(sourceFilePath);
|
||||
const destStream = fs().createWriteStream(destFilePath, {
|
||||
fd: destFile,
|
||||
autoClose: false,
|
||||
});
|
||||
@ -72,8 +75,8 @@ async function decodeBase64File(sourceFilePath: string, destFilePath: string) {
|
||||
// because even if the source has finished sending data, the destination might not have
|
||||
// finished receiving it and writing it to disk.
|
||||
destStream.on('finish', () => {
|
||||
fs.fdatasyncSync(destFile);
|
||||
fs.closeSync(destFile);
|
||||
fs().fdatasyncSync(destFile);
|
||||
fs().closeSync(destFile);
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
@ -140,7 +143,7 @@ async function processNoteResource(resource: ExtractedResource) {
|
||||
if (setId) resource.id = md5(Date.now() + Math.random());
|
||||
resource.size = 0;
|
||||
resource.dataFilePath = `${Setting.value('tempDir')}/${resource.id}.empty`;
|
||||
await fs.writeFile(resource.dataFilePath, '');
|
||||
await shim.fsDriver().writeFile(resource.dataFilePath, '');
|
||||
};
|
||||
|
||||
if (!resource.hasData) {
|
||||
@ -155,14 +158,14 @@ async function processNoteResource(resource: ExtractedResource) {
|
||||
throw new Error(`Cannot decode resource with encoding: ${resource.dataEncoding}`);
|
||||
}
|
||||
|
||||
const stats = fs.statSync(resource.dataFilePath);
|
||||
const stats = await shim.fsDriver().stat(resource.dataFilePath);
|
||||
resource.size = stats.size;
|
||||
|
||||
if (!resource.id) {
|
||||
// If no resource ID is present, the resource ID is actually the MD5
|
||||
// of the data. This ID will match the "hash" attribute of the
|
||||
// corresponding <en-media> tag. resourceId = md5(decodedData);
|
||||
resource.id = await md5File(resource.dataFilePath);
|
||||
resource.id = await shim.fsDriver().md5File(resource.dataFilePath);
|
||||
}
|
||||
|
||||
if (!resource.id || !resource.size) {
|
||||
@ -203,7 +206,7 @@ async function saveNoteResources(note: ExtractedNote) {
|
||||
const existingResource = await Resource.load(toSave.id);
|
||||
if (existingResource) continue;
|
||||
|
||||
await fs.move(resource.dataFilePath, Resource.fullPath(toSave), { overwrite: true });
|
||||
await shim.fsDriver().move(resource.dataFilePath, Resource.fullPath(toSave));
|
||||
await Resource.save(toSave, { isNew: true });
|
||||
resourcesCreated++;
|
||||
}
|
||||
@ -381,7 +384,7 @@ const parseNotes = async (parentFolderId: string, filePath: string, importOption
|
||||
notesTagged: 0,
|
||||
};
|
||||
|
||||
const stream = fs.createReadStream(fileToProcess);
|
||||
const stream = fs().createReadStream(fileToProcess);
|
||||
|
||||
const options = {};
|
||||
const strict = true;
|
||||
@ -547,7 +550,7 @@ const parseNotes = async (parentFolderId: string, filePath: string, importOption
|
||||
|
||||
noteResource.hasData = true;
|
||||
|
||||
fs.appendFileSync(noteResource.dataFilePath, text);
|
||||
fs().appendFileSync(noteResource.dataFilePath, text);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
if (!(n in noteResource)) (noteResource as any)[n] = '';
|
||||
|
@ -49,6 +49,7 @@ export interface Constants {
|
||||
appType: AppType;
|
||||
resourceDirName: string;
|
||||
resourceDir: string;
|
||||
pluginAssetDir: string;
|
||||
profileDir: string;
|
||||
rootProfileDir: string;
|
||||
tempDir: string;
|
||||
@ -217,6 +218,7 @@ class Setting extends BaseModel {
|
||||
appType: 'SET_ME' as any, // 'cli' or 'mobile'
|
||||
resourceDirName: '',
|
||||
resourceDir: '',
|
||||
pluginAssetDir: '',
|
||||
profileDir: '',
|
||||
rootProfileDir: '',
|
||||
tempDir: '',
|
||||
|
@ -18,7 +18,11 @@ import InteropService_Exporter_Md from './InteropService_Exporter_Md';
|
||||
import InteropService_Exporter_Md_frontmatter from './InteropService_Exporter_Md_frontmatter';
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import Module, { dynamicRequireModuleFactory, makeExportModule, makeImportModule } from './Module';
|
||||
import Module, { makeExportModule, makeImportModule } from './Module';
|
||||
import InteropService_Exporter_Html from './InteropService_Exporter_Html';
|
||||
import InteropService_Importer_EnexToHtml from './InteropService_Importer_EnexToHtml';
|
||||
import InteropService_Importer_EnexToMd from './InteropService_Importer_EnexToMd';
|
||||
import InteropService_Importer_OneNote from './InteropService_Importer_OneNote';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const { fileExtension } = require('../../path-utils');
|
||||
const EventEmitter = require('events');
|
||||
@ -76,7 +80,7 @@ export default class InteropService {
|
||||
description: _('Evernote Export File (as HTML)'),
|
||||
supportsMobile: false,
|
||||
outputFormat: ImportModuleOutputFormat.Html,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToHtml')),
|
||||
}, () => new InteropService_Importer_EnexToHtml()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'enex',
|
||||
@ -85,7 +89,7 @@ export default class InteropService {
|
||||
description: _('Evernote Export File (as Markdown)'),
|
||||
supportsMobile: false,
|
||||
isDefault: true,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToMd')),
|
||||
}, () => new InteropService_Importer_EnexToMd()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'enex',
|
||||
@ -94,7 +98,7 @@ export default class InteropService {
|
||||
description: _('Evernote Export Files (Directory, as HTML)'),
|
||||
supportsMobile: false,
|
||||
outputFormat: ImportModuleOutputFormat.Html,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToHtml')),
|
||||
}, () => new InteropService_Importer_EnexToHtml()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'enex',
|
||||
@ -102,7 +106,7 @@ export default class InteropService {
|
||||
sources: [FileSystemItem.Directory],
|
||||
description: _('Evernote Export Files (Directory, as Markdown)'),
|
||||
supportsMobile: false,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToMd')),
|
||||
}, () => new InteropService_Importer_EnexToMd()),
|
||||
|
||||
makeImportModule({
|
||||
format: 'html',
|
||||
@ -142,7 +146,7 @@ export default class InteropService {
|
||||
sources: [FileSystemItem.File],
|
||||
isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format)
|
||||
description: _('OneNote Notebook'),
|
||||
}, dynamicRequireModuleFactory('./InteropService_Importer_OneNote')),
|
||||
}, () => new InteropService_Importer_OneNote()),
|
||||
];
|
||||
|
||||
const exportModules = [
|
||||
@ -178,14 +182,14 @@ export default class InteropService {
|
||||
isNoteArchive: false,
|
||||
description: _('HTML File'),
|
||||
supportsMobile: false,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Exporter_Html')),
|
||||
}, () => new InteropService_Exporter_Html()),
|
||||
|
||||
makeExportModule({
|
||||
format: ExportModuleOutputFormat.Html,
|
||||
target: FileSystemItem.Directory,
|
||||
description: _('HTML Directory'),
|
||||
supportsMobile: false,
|
||||
}, dynamicRequireModuleFactory('./InteropService_Exporter_Html')),
|
||||
}, () => new InteropService_Exporter_Html()),
|
||||
];
|
||||
|
||||
this.defaultModules_ = (importModules as Module[]).concat(exportModules);
|
||||
|
@ -9,7 +9,7 @@ import { MarkupToHtml } from '@joplin/renderer';
|
||||
import { NoteEntity, ResourceEntity, ResourceLocalStateEntity } from '../database/types';
|
||||
import { contentScriptsToRendererRules } from '../plugins/utils/loadContentScripts';
|
||||
import { basename, friendlySafeFilename, rtrimSlashes, dirname } from '../../path-utils';
|
||||
import htmlpack from '@joplin/htmlpack';
|
||||
import packToString from '@joplin/htmlpack/packToString';
|
||||
const { themeStyle } = require('../../theme');
|
||||
const { escapeHtml } = require('../../string-utils.js');
|
||||
import { assetsToHeaders } from '@joplin/renderer';
|
||||
@ -19,6 +19,7 @@ import Logger from '@joplin/utils/Logger';
|
||||
import { parseRenderedNoteMetadata } from './utils';
|
||||
import ResourceLocalState from '../../models/ResourceLocalState';
|
||||
import { ResourceInfos } from '@joplin/renderer/types';
|
||||
import { fromFilename } from '../../mime-utils';
|
||||
|
||||
const logger = Logger.create('InteropService_Exporter_Html');
|
||||
|
||||
@ -138,13 +139,11 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
|
||||
if (metadata.printTitle && item.title) noteContent.push(`<div class="exported-note-title">${escapeHtml(item.title)}</div>`);
|
||||
if (result.html) noteContent.push(result.html);
|
||||
|
||||
const libRootPath = dirname(dirname(__dirname));
|
||||
|
||||
// We need to export all the plugin assets too and refer them from the header
|
||||
// The source path is a bit hard-coded but shouldn't change.
|
||||
for (let i = 0; i < result.pluginAssets.length; i++) {
|
||||
const asset = result.pluginAssets[i];
|
||||
const filePath = asset.pathIsAbsolute ? asset.path : `${libRootPath}/node_modules/@joplin/renderer/assets/${asset.name}`;
|
||||
const filePath = asset.pathIsAbsolute ? asset.path : `${Setting.value('pluginAssetDir')}/${asset.name}`;
|
||||
if (!(await shim.fsDriver().exists(filePath))) {
|
||||
logger.warn(`File does not exist and cannot be exported: ${filePath}`);
|
||||
} else {
|
||||
@ -175,8 +174,12 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
public async processResource(resource: ResourceEntity, filePath: string) {
|
||||
if (!this.resourceDir_) return;
|
||||
if (!await shim.fsDriver().exists(this.resourceDir_)) {
|
||||
await shim.fsDriver().mkdir(this.resourceDir_);
|
||||
}
|
||||
|
||||
const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`;
|
||||
await shim.fsDriver().copy(filePath, destResourcePath);
|
||||
const localState: ResourceLocalStateEntity = await ResourceLocalState.load(resource.id);
|
||||
@ -188,10 +191,37 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
|
||||
|
||||
public async close() {
|
||||
if (this.packIntoSingleFile_) {
|
||||
const tempFilePath = `${this.filePath_}.tmp`;
|
||||
await shim.fsDriver().move(this.filePath_, tempFilePath);
|
||||
await htmlpack(tempFilePath, this.filePath_);
|
||||
await shim.fsDriver().remove(tempFilePath);
|
||||
const mainHtml = await shim.fsDriver().readFile(this.filePath_, 'utf8');
|
||||
const resolveToAllowedDir = (path: string) => {
|
||||
// TODO: Enable this for all platforms -- at present, this is mobile-only.
|
||||
const restrictToDestDir = !!shim.mobilePlatform();
|
||||
if (restrictToDestDir) {
|
||||
return shim.fsDriver().resolveRelativePathWithinDir(this.destDir_, path);
|
||||
} else {
|
||||
return shim.fsDriver().resolve(this.destDir_, path);
|
||||
}
|
||||
};
|
||||
const packedHtml = await packToString(
|
||||
this.destDir_,
|
||||
mainHtml,
|
||||
{
|
||||
exists: (path) => {
|
||||
path = resolveToAllowedDir(path);
|
||||
return shim.fsDriver().exists(path);
|
||||
},
|
||||
readFileDataUri: async (path) => {
|
||||
path = resolveToAllowedDir(path);
|
||||
const mimeType = fromFilename(path);
|
||||
const content = await shim.fsDriver().readFile(path, 'base64');
|
||||
return `data:${mimeType};base64,${content}`;
|
||||
},
|
||||
readFileText: (path) => {
|
||||
path = resolveToAllowedDir(path);
|
||||
return shim.fsDriver().readFile(path, 'utf8');
|
||||
},
|
||||
},
|
||||
);
|
||||
await shim.fsDriver().writeFile(this.filePath_, packedHtml, 'utf8');
|
||||
|
||||
for (const d of this.createdDirs_) {
|
||||
await shim.fsDriver().remove(d);
|
||||
|
@ -3,7 +3,6 @@ import { ImportExportResult, ImportModuleOutputFormat, ImportOptions } from './t
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import { NoteEntity } from '../database/types';
|
||||
import { rtrimSlashes } from '../../path-utils';
|
||||
import * as AdmZip from 'adm-zip';
|
||||
import InteropService_Importer_Md from './InteropService_Importer_Md';
|
||||
import { join, resolve, normalize, sep, dirname } from 'path';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
@ -45,11 +44,9 @@ export default class InteropService_Importer_OneNote extends InteropService_Impo
|
||||
public async exec(result: ImportExportResult) {
|
||||
const sourcePath = rtrimSlashes(this.sourcePath_);
|
||||
const unzipTempDirectory = await this.temporaryDirectory_(true);
|
||||
const zip = new AdmZip(sourcePath);
|
||||
logger.info('Unzipping files...');
|
||||
zip.extractAllTo(unzipTempDirectory, false);
|
||||
const files = await shim.fsDriver().zipExtract({ source: sourcePath, extractTo: unzipTempDirectory });
|
||||
|
||||
const files = zip.getEntries();
|
||||
if (files.length === 0) {
|
||||
result.warnings.push('Zip file has no files.');
|
||||
return result;
|
||||
@ -60,7 +57,7 @@ export default class InteropService_Importer_OneNote extends InteropService_Impo
|
||||
const notebookBaseDir = join(unzipTempDirectory, baseFolder, sep);
|
||||
const outputDirectory2 = join(tempOutputDirectory, baseFolder);
|
||||
|
||||
const notebookFiles = zip.getEntries().filter(e => e.name !== '.onetoc2' && e.name !== 'OneNote_RecycleBin.onetoc2');
|
||||
const notebookFiles = files.filter(e => e.name !== '.onetoc2' && e.name !== 'OneNote_RecycleBin.onetoc2');
|
||||
const { oneNoteConverter } = shim.requireDynamic('@joplin/onenote-converter');
|
||||
|
||||
logger.info('Extracting OneNote to HTML');
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { _ } from '../../locale';
|
||||
import shim from '../../shim';
|
||||
import InteropService_Exporter_Base from './InteropService_Exporter_Base';
|
||||
import InteropService_Importer_Base from './InteropService_Importer_Base';
|
||||
import { ExportModuleOutputFormat, ExportOptions, FileSystemItem, ImportModuleOutputFormat, ImportOptions, ModuleType } from './types';
|
||||
@ -126,16 +125,5 @@ export const makeExportModule = (
|
||||
};
|
||||
};
|
||||
|
||||
// A module factory that uses dynamic requires.
|
||||
// TODO: This is currently only used because some importers/exporters import libraries that
|
||||
// don't work on mobile (e.g. htmlpack or fs). These importers/exporters should be migrated
|
||||
// to fs so that this can be removed.
|
||||
export const dynamicRequireModuleFactory = (fileName: string) => {
|
||||
return () => {
|
||||
const ModuleClass = shim.requireDynamic(fileName).default;
|
||||
return new ModuleClass();
|
||||
};
|
||||
};
|
||||
|
||||
type Module = ImportModule|ExportModule;
|
||||
export default Module;
|
||||
|
@ -68,6 +68,7 @@ import OcrService from '../services/ocr/OcrService';
|
||||
import { createWorker } from 'tesseract.js';
|
||||
import { reg } from '../registry';
|
||||
import { Store } from 'redux';
|
||||
import { dirname } from '@joplin/utils/path';
|
||||
import SyncTargetJoplinServerSAML from '../SyncTargetJoplinServerSAML';
|
||||
|
||||
// Each suite has its own separate data and temp directory so that multiple
|
||||
@ -198,6 +199,7 @@ Setting.setConstant('tempDir', baseTempDir);
|
||||
Setting.setConstant('cacheDir', baseTempDir);
|
||||
Setting.setConstant('resourceDir', baseTempDir);
|
||||
Setting.setConstant('pluginDataDir', `${profileDir}/profile/plugin-data`);
|
||||
Setting.setConstant('pluginAssetDir', `${dirname(require.resolve('@joplin/renderer'))}/assets`);
|
||||
Setting.setConstant('profileDir', profileDir);
|
||||
Setting.setConstant('rootProfileDir', rootProfileDir);
|
||||
Setting.setConstant('env', Env.Dev);
|
||||
|
@ -179,4 +179,7 @@ Haverbeke
|
||||
unfocusable
|
||||
unlocker
|
||||
Tiktok
|
||||
topagency
|
||||
topagency
|
||||
esbuild
|
||||
mapbox
|
||||
outfile
|
||||
|
@ -7,9 +7,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"browser": {
|
||||
"jsdom": false
|
||||
},
|
||||
"browser": "lib/turndown.browser.cjs.js",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "4.4.2",
|
||||
"html-entities": "1.4.0",
|
||||
@ -41,12 +39,12 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build-all": "npm run build-cjs && npm run build-es && npm run build-umd && npm run build-iife",
|
||||
"build": "rollup -c config/rollup.config.cjs.mjs",
|
||||
"build-cjs": "rollup -c config/rollup.config.cjs.mjs && rollup -c config/rollup.config.browser.cjs.mjs",
|
||||
"build-es": "rollup -c config/rollup.config.es.mjs && rollup -c config/rollup.config.browser.es.mjs",
|
||||
"build-umd": "rollup -c config/rollup.config.umd.mjs && rollup -c config/rollup.config.browser.umd.mjs",
|
||||
"build-iife": "rollup -c config/rollup.config.iife.mjs",
|
||||
"build-test": "browserify test/turndown-test.js --outfile test/turndown-test.browser.js",
|
||||
"build": "npm run build-cjs",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"gitHead": "05a29b450962bf05a8642bbd39446a1f679a96ba"
|
||||
|
27
readme/dev/spec/desktop_bundling.md
Normal file
27
readme/dev/spec/desktop_bundling.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Desktop app bundling
|
||||
|
||||
For performance and to reduce the application size, the desktop app is bundled with [esbuild](https://esbuild.github.io/). Bundling packs most of the desktop application's JavaScript into one or two JavaScript files. This occurs as a part of both `yarn dist` and `yarn start`.
|
||||
|
||||
## Why bundle the app?
|
||||
|
||||
- **Performance**: Bundling the app [is recommended by the Electron performance guide](https://www.electronjs.org/docs/latest/tutorial/performance#7-bundle-your-code). The guide states that "Loading modules is a surprisingly expensive operation, especially on Windows." Bundling the application reduces the number of `require` calls.
|
||||
- **Application size**: Bundling can reduce the size of the app created by `yarn dist`.
|
||||
|
||||
## How does bundling reduce the application size?
|
||||
|
||||
Bundling allows both:
|
||||
1. Reducing the size of the JavaScript included in the app through [minification](https://esbuild.github.io/api/#minify), and
|
||||
2. Reducing the number of dependencies included in `node_modules` in the version of the app built with `electron-builder`. Dependencies often include files unnecessary for the final build (e.g. README images, test files).
|
||||
|
||||
|
||||
## Excluding dependencies from `node_modules`
|
||||
|
||||
After bundling the app, most dependencies are completely included within `main.bundle.js` or `main-html.bundle.js`. As a result, the copies of these dependencies in `node_modules` are completely unused.
|
||||
|
||||
Some dependencies need to be included in `node_modules` at runtime. This is the case, for example, if the dependency needs to be required with `shim.requireDynamic`, or if the dependency includes native `.node` assets that can't be bundled. For example, `sqlite3` includes native `.node` assets that need to be in `node_modules` at runtime.
|
||||
|
||||
Electron-builder can be instructed to exclude dependencies from the built application [by moving them to `devDependencies`](https://github.com/electron-userland/electron-builder/blob/84657680ba5688f1594bc77be3df5c2c78125723/README.md?plain=1#L73). A dependency should only be in the production `dependencies` if it needs to be included in `node_modules` at runtime.
|
||||
|
||||
## Determining what contributes to the bundle size
|
||||
|
||||
To see what contributes to the size of the application's bundled JavaScript, consider using [esbuild's bundle size analyzer](https://esbuild.github.io/analyze/). The size analyzer accepts esbuild metafiles. To build these metafiles, manually run `yarn bundle`. Bundle metadata will be written to the `app-desktop` directory as `.meta.json` files.
|
@ -2,6 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"esModuleInterop": false,
|
||||
//"lib": ["es2015", "es2020.string", "dom", "dom.iterable"],
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
267
yarn.lock
267
yarn.lock
@ -7384,6 +7384,181 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.3"
|
||||
conditions: os=aix & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/android-arm64@npm:0.25.3"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-arm@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/android-arm@npm:0.25.3"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/android-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/android-x64@npm:0.25.3"
|
||||
conditions: os=android & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/darwin-arm64@npm:0.25.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/darwin-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/darwin-x64@npm:0.25.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/freebsd-arm64@npm:0.25.3"
|
||||
conditions: os=freebsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/freebsd-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/freebsd-x64@npm:0.25.3"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-arm64@npm:0.25.3"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-arm@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-arm@npm:0.25.3"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ia32@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-ia32@npm:0.25.3"
|
||||
conditions: os=linux & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-loong64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-loong64@npm:0.25.3"
|
||||
conditions: os=linux & cpu=loong64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-mips64el@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-mips64el@npm:0.25.3"
|
||||
conditions: os=linux & cpu=mips64el
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-ppc64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-ppc64@npm:0.25.3"
|
||||
conditions: os=linux & cpu=ppc64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-riscv64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-riscv64@npm:0.25.3"
|
||||
conditions: os=linux & cpu=riscv64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-s390x@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-s390x@npm:0.25.3"
|
||||
conditions: os=linux & cpu=s390x
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/linux-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/linux-x64@npm:0.25.3"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/netbsd-arm64@npm:0.25.3"
|
||||
conditions: os=netbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/netbsd-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/netbsd-x64@npm:0.25.3"
|
||||
conditions: os=netbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/openbsd-arm64@npm:0.25.3"
|
||||
conditions: os=openbsd & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/openbsd-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/openbsd-x64@npm:0.25.3"
|
||||
conditions: os=openbsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/sunos-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/sunos-x64@npm:0.25.3"
|
||||
conditions: os=sunos & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-arm64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/win32-arm64@npm:0.25.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-ia32@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/win32-ia32@npm:0.25.3"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/win32-x64@npm:0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "@esbuild/win32-x64@npm:0.25.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
|
||||
@ -8727,6 +8902,7 @@ __metadata:
|
||||
"@joplin/default-plugins": ~3.4
|
||||
"@joplin/editor": ~3.4
|
||||
"@joplin/lib": ~3.4
|
||||
"@joplin/onenote-converter": ~3.4
|
||||
"@joplin/renderer": ~3.4
|
||||
"@joplin/tools": ~3.4
|
||||
"@joplin/utils": ~3.4
|
||||
@ -8752,6 +8928,7 @@ __metadata:
|
||||
electron-builder: 24.13.3
|
||||
electron-updater: 6.2.1
|
||||
electron-window-state: 5.0.3
|
||||
esbuild: ^0.25.3
|
||||
formatcoords: 1.1.3
|
||||
fs-extra: 11.2.0
|
||||
glob: 10.4.5
|
||||
@ -24052,6 +24229,92 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.25.3":
|
||||
version: 0.25.3
|
||||
resolution: "esbuild@npm:0.25.3"
|
||||
dependencies:
|
||||
"@esbuild/aix-ppc64": 0.25.3
|
||||
"@esbuild/android-arm": 0.25.3
|
||||
"@esbuild/android-arm64": 0.25.3
|
||||
"@esbuild/android-x64": 0.25.3
|
||||
"@esbuild/darwin-arm64": 0.25.3
|
||||
"@esbuild/darwin-x64": 0.25.3
|
||||
"@esbuild/freebsd-arm64": 0.25.3
|
||||
"@esbuild/freebsd-x64": 0.25.3
|
||||
"@esbuild/linux-arm": 0.25.3
|
||||
"@esbuild/linux-arm64": 0.25.3
|
||||
"@esbuild/linux-ia32": 0.25.3
|
||||
"@esbuild/linux-loong64": 0.25.3
|
||||
"@esbuild/linux-mips64el": 0.25.3
|
||||
"@esbuild/linux-ppc64": 0.25.3
|
||||
"@esbuild/linux-riscv64": 0.25.3
|
||||
"@esbuild/linux-s390x": 0.25.3
|
||||
"@esbuild/linux-x64": 0.25.3
|
||||
"@esbuild/netbsd-arm64": 0.25.3
|
||||
"@esbuild/netbsd-x64": 0.25.3
|
||||
"@esbuild/openbsd-arm64": 0.25.3
|
||||
"@esbuild/openbsd-x64": 0.25.3
|
||||
"@esbuild/sunos-x64": 0.25.3
|
||||
"@esbuild/win32-arm64": 0.25.3
|
||||
"@esbuild/win32-ia32": 0.25.3
|
||||
"@esbuild/win32-x64": 0.25.3
|
||||
dependenciesMeta:
|
||||
"@esbuild/aix-ppc64":
|
||||
optional: true
|
||||
"@esbuild/android-arm":
|
||||
optional: true
|
||||
"@esbuild/android-arm64":
|
||||
optional: true
|
||||
"@esbuild/android-x64":
|
||||
optional: true
|
||||
"@esbuild/darwin-arm64":
|
||||
optional: true
|
||||
"@esbuild/darwin-x64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/freebsd-x64":
|
||||
optional: true
|
||||
"@esbuild/linux-arm":
|
||||
optional: true
|
||||
"@esbuild/linux-arm64":
|
||||
optional: true
|
||||
"@esbuild/linux-ia32":
|
||||
optional: true
|
||||
"@esbuild/linux-loong64":
|
||||
optional: true
|
||||
"@esbuild/linux-mips64el":
|
||||
optional: true
|
||||
"@esbuild/linux-ppc64":
|
||||
optional: true
|
||||
"@esbuild/linux-riscv64":
|
||||
optional: true
|
||||
"@esbuild/linux-s390x":
|
||||
optional: true
|
||||
"@esbuild/linux-x64":
|
||||
optional: true
|
||||
"@esbuild/netbsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/netbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-arm64":
|
||||
optional: true
|
||||
"@esbuild/openbsd-x64":
|
||||
optional: true
|
||||
"@esbuild/sunos-x64":
|
||||
optional: true
|
||||
"@esbuild/win32-arm64":
|
||||
optional: true
|
||||
"@esbuild/win32-ia32":
|
||||
optional: true
|
||||
"@esbuild/win32-x64":
|
||||
optional: true
|
||||
bin:
|
||||
esbuild: bin/esbuild
|
||||
checksum: 1f9af51aa1d7d1f57e7294823d19ed69b0f6da413b7b0e8123abcebd1bb4011ef19961e2e6679c07301fcd00a85c4d102160fc40a91c25ceeaf594932509d84d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"escalade@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "escalade@npm:3.1.1"
|
||||
@ -37986,7 +38249,7 @@ __metadata:
|
||||
|
||||
"pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::locator=root%40workspace%3A.":
|
||||
version: 3.11.174
|
||||
resolution: "pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::version=3.11.174&hash=4ecadb&locator=root%40workspace%3A."
|
||||
resolution: "pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::version=3.11.174&hash=d35ec8&locator=root%40workspace%3A."
|
||||
dependencies:
|
||||
path2d-polyfill: ^2.0.1
|
||||
dependenciesMeta:
|
||||
@ -37994,7 +38257,7 @@ __metadata:
|
||||
optional: true
|
||||
path2d-polyfill:
|
||||
optional: true
|
||||
checksum: bc7597789b13b3ea59ee4ff29db40a15f0a20094ef8f6fa9ba59a80db0501a9b94d0fb3602515666057ebe9c92e59d938ac157f4d2852a125ccc1511c9d8adf6
|
||||
checksum: aef2f87e478a66541311228716cd91834d4c83320d0fbf9bb0fcbb427f503c125edfb6c5def77d2d4c57db27b92de652752783b50c3edb4a059c4d2b84e1c913
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user