From fdebfae0ca9d5d488e3fb44eb20ed45775e5f443 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 21 Jan 2019 16:06:25 +0000 Subject: [PATCH] Lazy-load SimpleMDE dependency in MD card (reduces main bundle by 260KB) no issue - use broccoli-funnel to avoid polluting the `dist/` dir with unused build artefacts - load codemirror and simplemde CSS up-front to avoid duplicate styles and specificity problems - pull simplemde dependency out into a separate JS file and lazy-load in `{{gh-simplemde}}` component to reduce main bundle size. Before/after: - `vendor.min.js 3.58 MB (795.88 KB gzipped)` - `vendor.min.js 3.32 MB (710.66 KB gzipped)` --- app/components/gh-cm-editor.js | 1 - app/components/gh-simplemde.js | 24 ++++++++----- app/styles/layouts/editor.css | 5 +++ ember-cli-build.js | 63 +++++++++++++++++++++++++++------- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/app/components/gh-cm-editor.js b/app/components/gh-cm-editor.js index 63556526e9..60ddc40a3e 100644 --- a/app/components/gh-cm-editor.js +++ b/app/components/gh-cm-editor.js @@ -65,7 +65,6 @@ const CmEditorComponent = Component.extend({ let loader = this.get('lazyLoader'); yield RSVP.all([ - loader.loadStyle('codemirror', 'assets/codemirror/codemirror.css'), loader.loadScript('codemirror', 'assets/codemirror/codemirror.js') ]); diff --git a/app/components/gh-simplemde.js b/app/components/gh-simplemde.js index a5efa7b918..2bfa21370d 100644 --- a/app/components/gh-simplemde.js +++ b/app/components/gh-simplemde.js @@ -4,8 +4,11 @@ import config from 'ghost-admin/config/environment'; import {assign} from '@ember/polyfills'; import {computed} from '@ember/object'; import {isEmpty} from '@ember/utils'; +import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; export default TextArea.extend({ + lazyLoader: service(), // Public attributes autofocus: false, @@ -60,6 +63,18 @@ export default TextArea.extend({ // instantiate the editor with the contents of value didInsertElement() { this._super(...arguments); + this.initSimpleMDE.perform(); + }, + + willDestroyElement() { + this.onEditorDestroy(); + this._editor.toTextArea(); + delete this._editor; + this._super(...arguments); + }, + + initSimpleMDE: task(function* () { + yield this.lazyLoader.loadScript('simplemde', 'assets/simplemde/simplemde.js'); let editorOptions = assign( {element: document.getElementById(this.elementId)}, @@ -97,12 +112,5 @@ export default TextArea.extend({ } this.onEditorInit(this._editor); - }, - - willDestroyElement() { - this.onEditorDestroy(); - this._editor.toTextArea(); - delete this._editor; - this._super(...arguments); - } + }) }); diff --git a/app/styles/layouts/editor.css b/app/styles/layouts/editor.css index 50b68cb08d..0e5fe8d7ec 100644 --- a/app/styles/layouts/editor.css +++ b/app/styles/layouts/editor.css @@ -378,6 +378,11 @@ /* SimpleMDE editor /* ---------------------------------------------------------- */ +/* ensure there's no flash of unstyled textarea while initializing */ +.gh-markdown-editor textarea { + visibility: hidden; +} + .gh-editor-title { padding: 0; } diff --git a/ember-cli-build.js b/ember-cli-build.js index 311921fe9f..f2f18c51de 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -6,20 +6,19 @@ const concat = require('broccoli-concat'); const mergeTrees = require('broccoli-merge-trees'); const uglify = require('broccoli-uglify-sourcemap'); const CleanCSS = require('broccoli-clean-css'); +const Funnel = require('broccoli-funnel'); const environment = EmberApp.env(); const isProduction = environment === 'production'; -let assetLocation, codemirrorAssets; -assetLocation = function (fileName) { +const assetLocation = function (fileName) { if (isProduction) { fileName = fileName.replace('.', '.min.'); } return `/assets/${fileName}`; }; -codemirrorAssets = function () { +const codemirrorAssets = function () { let codemirrorFiles = [ - // 'lib/codemirror.css', 'theme/xq-light.css', 'lib/codemirror.js', 'mode/htmlmixed/htmlmixed.js', @@ -45,23 +44,56 @@ codemirrorAssets = function () { sourceMapConfig: {enabled: false} }); - let cssTree = concat(tree, { - outputFile: 'assets/codemirror/codemirror.css', - inputFiles: ['**/*.css'] + if (isProduction) { + jsTree = uglify(jsTree); + } + + let mergedTree = mergeTrees([tree, jsTree]); + return new Funnel(mergedTree, {include: ['assets/**/*', 'theme/**/*']}); + } + }; + + // put the files in vendor ready for importing into the test-support file + if (environment === 'development') { + config.vendor = codemirrorFiles; + } + + return config; +}; + +const simplemdeAssets = function () { + let simplemdeFiles = [ + 'debug/simplemde.js' + ]; + + if (environment === 'test') { + return {import: simplemdeFiles}; + } + + let config = {}; + + config.public = { + include: simplemdeFiles, + destDir: '/', + processTree(tree) { + let jsTree = concat(tree, { + outputFile: 'assets/simplemde/simplemde.js', + inputFiles: ['debug/simplemde.js'], + sourceMapConfig: {enabled: false} }); if (isProduction) { jsTree = uglify(jsTree); - cssTree = new CleanCSS(cssTree); } - return mergeTrees([tree, jsTree, cssTree]); + let mergedTree = mergeTrees([tree, jsTree]); + return new Funnel(mergedTree, {include: ['assets/**/*']}); } }; // put the files in vendor ready for importing into the test-support file if (environment === 'development') { - config.vendor = codemirrorFiles; + config.vendor = simplemdeFiles; } return config; @@ -103,7 +135,8 @@ module.exports = function (defaults) { } }, nodeAssets: { - codemirror: codemirrorAssets() + codemirror: codemirrorAssets(), + simplemde: simplemdeAssets() }, svgJar: { strategy: 'inline', @@ -130,14 +163,17 @@ module.exports = function (defaults) { // Stop: Normalize app.import('node_modules/normalize.css/normalize.css'); - app.import('node_modules/simplemde/debug/simplemde.css'); + + // 'dem Styles + // import codemirror + simplemde styles rather than lazy-loading so that + // our overrides work correctly + app.import('node_modules/simplemde/dist/simplemde.min.css'); // 'dem Scripts app.import('node_modules/google-caja-bower/html-css-sanitizer-bundle.js'); app.import('node_modules/keymaster/keymaster.js'); app.import('node_modules/@tryghost/mobiledoc-kit/dist/amd/mobiledoc-kit.js'); app.import('node_modules/@tryghost/mobiledoc-kit/dist/amd/mobiledoc-kit.map'); - app.import('node_modules/simplemde/debug/simplemde.js'); app.import('node_modules/reframe.js/dist/noframe.es.js', { using: [ {transformation: 'es6', as: 'noframe.js'} @@ -148,6 +184,7 @@ module.exports = function (defaults) { // that tests don't break when running via http://localhost:4200/tests if (app.env === 'development') { app.import('vendor/codemirror/lib/codemirror.js', {type: 'test'}); + app.import('vendor/simplemde/debug/simplemde.js', {type: 'test'}); } return app.toTree();