Improve contacts window launch perf by lazy loading composer support, scanning less of fs for themes

This commit is contained in:
Ben Gotow 2019-09-26 13:50:14 -05:00
parent 07abd6cb71
commit 0c2b0eb03b
74 changed files with 105 additions and 98 deletions

View file

@ -4,9 +4,9 @@ module.exports = grunt => {
src: ['internal_packages/**/*.less', 'dot-nylas/**/*.less', 'static/**/*.less'],
options: {
less: {
paths: ['static', 'static/base/'],
paths: ['static', 'static/style/'],
},
imports: ['static/base/*.less'],
imports: ['static/style/*.less'],
},
},
});

View file

@ -105,7 +105,7 @@ export function activate() {
const i = document.createElement('i');
i.className = 'fa fa-list';
i.style.position = 'absolute';
i.style.top = '0';
i.style.top = '-20px';
document.body.appendChild(i);
}, 1000);
}

View file

@ -7,6 +7,7 @@ import path from 'path';
import { EventedIFrame } from 'mailspring-component-kit';
import Package from '../../../src/package';
import LessCompileCache from '../../../src/less-compile-cache';
import _ from 'underscore';
interface ThemeOptionProps {
theme: Package;
@ -33,10 +34,10 @@ class ThemeOption extends React.Component<ThemeOptionProps> {
}
_getImportPaths() {
return [
return _.uniq([
this.props.theme.getStylesheetsPath(),
AppEnv.themes.getBaseTheme().getStylesheetsPath(),
];
]);
}
_loadStylesheet(stylesheetPath) {
@ -58,7 +59,7 @@ class ThemeOption extends React.Component<ThemeOptionProps> {
`${resourcePath}/internal_packages/theme-picker/preview-styles`,
this.props.theme.getStylesheetsPath()
);
let varImports = `@import "../../../static/base/ui-variables";`;
let varImports = `@import "base/ui-variables";`;
if (fs.existsSync(`${this.props.theme.getStylesheetsPath()}/ui-variables.less`)) {
varImports += `@import "${themeVarPath}/ui-variables";`;
}

View file

@ -14,7 +14,7 @@
line-height: 23px;
background: @input-bg;
color: @text-color;
margin-top: (38px - 23px) / 2;
margin-top: 5px;
padding-left: @padding-xs-horizontal;
padding-right: @padding-xs-horizontal;
border-radius: @border-radius-base;

View file

@ -27,7 +27,7 @@
@text-color-inverse: white;
@text-color-inverse-subtle: fadeout(@text-color-inverse, 20%);
@text-color-inverse-very-subtle: fadeout(@text-color-inverse, 50%);
@text-color-heading: #FFF;
@text-color-heading: #fff;
@text-color-link: @accent-primary;
@text-color-link-hover: @accent-primary-dark;

View file

@ -83,12 +83,6 @@ body.platform-win32 {
}
}
.sheet-toolbar .btn-toolbar {
height: 2em !important;
line-height: 1 !important;
margin-top: 6px !important;
}
/**
* Dropdown
*/

View file

@ -1,4 +1,4 @@
@import '../../../static/base/ui-variables';
@import 'base/ui-variables';
@import 'variables';
#account-switcher .primary-item .name {

View file

@ -1,5 +1,5 @@
/* all theme ui-variables files should inherit from the base one to ensure
that all variables are defined. */
@import 'base/ui-variables';
@import 'ui-variables';
/* ui-light is a fake theme that just falls through to the app's base styles. */

View file

@ -7,7 +7,6 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import _ from 'underscore';
import _str from 'underscore.string';
import { jasmine } from './jasmine';
export default class TimeReporter extends jasmine.Reporter {
@ -73,7 +72,7 @@ export default class TimeReporter extends jasmine.Reporter {
if (index === 0) {
return `${description}`;
} else {
return `${memo}\n${_str.repeat(' ', index)}${description}`;
return `${memo}\n${' '.repeat(index)}${description}`;
}
};
this.description = _.reduce(stack, reducer, '');

View file

@ -277,7 +277,7 @@ export default class WindowManager {
width: 800,
height: 500,
toolbar: true,
hidden: false,
hidden: true,
};
// The SPEC_WINDOW gets passed its own bootstrapScript

View file

@ -106,9 +106,7 @@ class EmptyListDataSource extends ListDataSource {
itemsCurrentlyInViewMatching() {
return [];
}
setRetainedRange() {
return;
}
setRetainedRange() {}
}
class DumbArrayDataSource<T extends Model> extends ListDataSource {
@ -139,7 +137,5 @@ class DumbArrayDataSource<T extends Model> extends ListDataSource {
itemsCurrentlyInViewMatching(matchFn: (item: T) => boolean) {
return this._items.filter(matchFn);
}
setRetainedRange() {
return;
}
setRetainedRange() {}
}

View file

@ -189,7 +189,6 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
componentDidMount() {
window.addEventListener('resize', this.onWindowResize, true);
this.setupDataSource(this.props.dataSource);
this.updateRangeState();
}
componentWillReceiveProps(nextProps) {
@ -214,7 +213,7 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
prevProps.itemHeight !== this.props.itemHeight ||
prevProps.dataSource !== this.props.dataSource
) {
this.updateRangeState();
this.updateRangeStateIfChanged();
}
if (!this._cleanupAnimationTimeout) {
@ -232,7 +231,7 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
onWindowResize = () => {
if (this._onWindowResize == null) {
this._onWindowResize = _.debounce(this.updateRangeState, 50);
this._onWindowResize = _.debounce(this.updateRangeStateIfChanged, 50);
}
this._onWindowResize();
};
@ -240,7 +239,7 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
onScroll = () => {
// If we've shifted enough pixels from our previous scrollTop to require
// new rows to be rendered, update our state!
this.updateRangeState();
this.updateRangeStateIfChanged();
};
onCleanupAnimatingItems = () => {
@ -264,10 +263,11 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
setupDataSource(dataSource) {
this._unlisten();
this._unlisten = dataSource.listen(() => {
this.setState(this.buildStateForRange());
});
this.setState(this.buildStateForRange({ start: -1, end: -1, dataSource }));
this._unlisten = dataSource.listen(() => this.setState(this.buildStateForRange()));
const range = this.getRange();
this.props.dataSource.setRetainedRange(range);
this.setState(this.buildStateForRange({ ...range, dataSource }));
}
getRowsToRender() {
@ -307,7 +307,7 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
this._scrollRegion.scrollTop += height * direction;
}
updateRangeState() {
getRange() {
if (!this._scrollRegion) {
return;
}
@ -323,23 +323,21 @@ export class ListTabular extends Component<ListTabularProps, ListTabularState> {
// we have items to move to and then scroll to.
rangeStart = Math.max(0, rangeStart - 2);
rangeEnd = Math.min(rangeEnd + 2, this.state.count + 1);
return { start: rangeStart, end: rangeEnd };
}
updateRangeStateIfChanged() {
const range = this.getRange();
// Final sanity check to prevent needless work
const shouldNotUpdate =
rangeEnd === this.state.renderedRangeEnd && rangeStart === this.state.renderedRangeStart;
if (shouldNotUpdate) {
return;
if (
range.end !== this.state.renderedRangeEnd ||
range.start !== this.state.renderedRangeStart
) {
this.updateRangeStateFiring = true;
this.props.dataSource.setRetainedRange(range);
this.setState(this.buildStateForRange(range));
}
this.updateRangeStateFiring = true;
this.props.dataSource.setRetainedRange({
start: rangeStart,
end: rangeEnd,
});
const nextState = this.buildStateForRange({ start: rangeStart, end: rangeEnd });
this.setState(nextState);
}
buildStateForRange(args: { start?: number; end?: number; dataSource?: ListDataSource } = {}) {

View file

@ -49,8 +49,7 @@ class ContactStore extends MailspringStore {
.order(Contact.attributes.refs.descending());
return (query.then(async _results => {
// remove query results that were already found in ranked contacts
let results = this._distinctByEmail(_results);
let results = this._distinctByEmail(this._omitFindInMailDisabled(_results));
for (const ext of extensions) {
results = await ext.findAdditionalContacts(search, results);
}
@ -69,7 +68,7 @@ class ContactStore extends MailspringStore {
.where(Contact.attributes.hidden.equal(false))
.order(Contact.attributes.refs.descending())
.then(async _results => {
let results = this._distinctByEmail(_results);
let results = this._distinctByEmail(this._omitFindInMailDisabled(_results));
if (results.length > limit) {
results.length = limit;
}
@ -145,6 +144,13 @@ class ContactStore extends MailspringStore {
);
}
_omitFindInMailDisabled(results: Contact[]) {
// remove results that the user has asked not to see. (Cheaper to do this in JS
// than construct a WHERE clause that makes SQLite's index selection non-obvious.)
const findInMailDisabled = AppEnv.config.get('core.contacts.findInMailDisabled');
return results.filter(r => !(r.source === 'mail' && findInMailDisabled.includes(r.accountId)));
}
_distinctByEmail(contacts: Contact[]) {
// remove query results that are duplicates, prefering ones that have names
const uniq: { [email: string]: Contact } = {};

View file

@ -1,7 +1,6 @@
import MailspringStore from 'mailspring-store';
import { Editor } from 'slate';
import { Editor, Value } from 'slate';
import { Conversion } from '../../components/composer-editor/composer-support';
import RegExpUtils from '../../regexp-utils';
import { localized } from '../../intl';
@ -20,7 +19,30 @@ import { SyncbackDraftTask } from '../tasks/syncback-draft-task';
export type MessageWithEditorState = Message & { bodyEditorState: any };
const { convertFromHTML, convertToHTML, convertToShapeWithoutContent } = Conversion;
/*
Note: This is a bit of a hack, but pulling in composer-support is what triggers slate,
slate-react, etc. to be loaded in windows where the components are not actually in use.
By lazily resolving this import, we can save ~400ms of window start-up time.
*/
let Conversion = null;
function resolveConversion() {
if (Conversion) return;
Conversion = require('../../components/composer-editor/composer-support').Conversion;
}
function convertFromHTML(html: string) {
resolveConversion();
return Conversion.convertFromHTML(html);
}
function convertToHTML(value: Value) {
resolveConversion();
return Conversion.convertToHTML(value);
}
function convertToShapeWithoutContent(value: Value) {
resolveConversion();
return Conversion.convertToShapeWithoutContent(value);
}
const MetadataChangePrefix = 'metadata.';
let DraftStore = null;

View file

@ -1,7 +1,6 @@
import { Task } from './task';
import { AttributeValues } from '../models/model';
import Attributes from '../attributes';
import ICAL from 'ical.js';
import {
localized,
ICSParticipantStatus,
@ -12,6 +11,8 @@ import {
Actions,
} from 'mailspring-exports';
let ICAL: typeof import('ical.js') = null;
export class EventRSVPTask extends Task {
ics: string;
icsRSVPStatus: ICSParticipantStatus;
@ -56,6 +57,9 @@ export class EventRSVPTask extends Task {
icsOriginalData: string;
icsRSVPStatus: ICSParticipantStatus;
}) {
if (!ICAL) {
ICAL = require('ical.js');
}
const jcalData = ICAL.parse(icsOriginalData);
const comp = new ICAL.Component(jcalData);
const event = new ICAL.Event(comp.getFirstSubcomponent('vevent'));

View file

@ -7,21 +7,25 @@ module.exports = exports = {};
// Because requiring files the first time they're used hurts performance, we
// automatically load components slowly in the background using idle cycles.
setTimeout(() => {
const remaining = Object.keys(module.exports);
const fn = deadline => {
let key = null;
let bogus = 0; // eslint-disable-line
while ((key = remaining.pop())) {
bogus += module.exports[key] ? 1 : 0;
if (deadline.timeRemaining() <= 0) {
window.requestIdleCallback(fn, { timeout: 5000 });
return;
if (AppEnv.isMainWindow()) {
setTimeout(() => {
const remaining = Object.keys(module.exports);
const fn = deadline => {
let key = null;
let bogus = 0; // eslint-disable-line
while ((key = remaining.pop())) {
bogus += module.exports[key] ? 1 : 0;
if (deadline.timeRemaining() <= 0) {
window.requestIdleCallback(fn, { timeout: 5000 });
return;
}
}
}
};
window.requestIdleCallback(fn, { timeout: 5000 });
}, 500);
};
window.requestIdleCallback(fn, { timeout: 5000 });
}, 500);
} else {
// just wait for them to be required
}
const resolveExport = (requireValue, name) => {
return requireValue.default || requireValue[name] || requireValue;

View file

@ -12,8 +12,8 @@ export default class LessCompileCache {
constructor({ configDirPath, resourcePath, importPaths = [] }) {
this.lessSearchPaths = [
path.join(resourcePath, 'static', 'base'),
path.join(resourcePath, 'static'),
path.join(resourcePath, 'static', 'style'),
path.join(resourcePath, 'static', 'style', 'base'),
];
this.cache = new LessCache({

View file

@ -72,8 +72,7 @@ export default class ThemeManager {
reloadCoreStyles() {
console.log('Reloading /static and /internal_packages to incorporate LESS changes');
const reloadStylesIn = folder => {
fs
.listTreeSync(folder)
fs.listTreeSync(folder)
.filter(stylePath => stylePath.endsWith('.less'))
.forEach(stylePath => {
const styleEl = document.head.querySelector(`[source-path="${stylePath}"]`);
@ -171,8 +170,8 @@ export default class ThemeManager {
}
loadStaticStylesheets() {
this.requireStylesheet('../static/index');
this.requireStylesheet('../static/email-frame');
this.requireStylesheet('../static/style/index');
this.requireStylesheet('../static/style/email-frame');
}
resolveStylesheetPath(stylesheetPath) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -60,7 +60,7 @@ mailspring-workspace {
}
.toolbar-window-controls {
margin-top: 9px;
margin-top: 7px;
margin-left: @spacing-half /*rtl:ignore */;
order: -1000 /*rtl: 1000*/;
min-width: 72px;
@ -164,33 +164,17 @@ body.is-blurred {
color: @text-color-heading;
}
.layout-mode-popout {
.sheet-toolbar {
background: @background-primary;
height: 35px;
min-height: 35px;
max-height: 35px;
.btn-toolbar {
margin-top: 6px;
}
}
.toolbar-window-controls {
margin-top: 7px;
}
}
.sheet-toolbar {
position: relative;
-webkit-app-region: drag;
border-bottom: 1px solid darken(@toolbar-background-color, 9%);
width: 100%;
height: 38px;
height: 34px;
// prevent flexbox from ever, ever resizing toolbars, no matter
// how much it thinks other content is being squished
min-height: 38px;
max-height: 38px;
min-height: 34px;
max-height: 34px;
// cover up the vertical resizing separators, so the toolbar appears
// to be one continuous bar.
@ -214,7 +198,7 @@ body.is-blurred {
left: 50%;
transform: translateX(-50%);
-webkit-app-region: drag;
line-height: 36px;
line-height: 34px;
&:hover {
cursor: default;
}
@ -249,7 +233,7 @@ body.is-blurred {
}
.btn-toolbar {
margin-top: 8px;
margin-top: 5px;
margin-left: @spacing-three-quarters;
margin-right: 0;
flex-shrink: 0;
@ -266,7 +250,7 @@ body.is-blurred {
}
}
.btn-toolbar:only-of-type {
margin-right: @spacing-three-quarters;
margin-right: 6px;
}
.button-group {