Skip to content

Commit

Permalink
Add frontend live / hot reload workflow
Browse files Browse the repository at this point in the history
The frontend workflow requires launching webpack in watch mode. This is
then followed by manually copying the built bundles to the output
directory of the site, or terminating and then relaunching the MarkBind
command.

With a frontend entry point setup in the core-web package, and the
MarkBind /vue-strap fork merged into the main monorepo, let's take
advantage of this and add a live / hot reload workflow.
This provides a near instant edit-feedback development cycle that makes
frontend development much quicker.

This is integrated into the markbind serve command using the --dev
option.
  • Loading branch information
ang-zeyu committed Aug 16, 2020
1 parent 0f5fd9b commit 07c364e
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 23 deletions.
16 changes: 14 additions & 2 deletions docs/devGuide/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,21 @@ The sections below has more information about various stages of submitting a PR.

## Writing code

Use JavaScript ES6 features if possible for better performance, e.g. Promise instead of callback.
#### General tips

Do note [our style guides](styleGuides.html).
* Use JavaScript ES6 features if possible for better performance, e.g. Promise instead of callback.
* Do note [our style guides](styleGuides.html).

#### Editing frontend features

We update the frontend `markbind.min.js` and `markbind.min.css` bundles during release only, and not in pull requests.

Hence, if you need to view the latest frontend changes (relating to `packages/core-web` or `packages/vue-components`), you can either:
1. Run `npm run build:web` in the root directory, which builds the above bundles,
then run your markbind-cli [command](https://markbind.org/userGuide/cliCommands.html) of choice.
2. Run `markbind serve -d` (with any other applicable options). (**recommended**)<br>
This adds the necessary webpack middlewares to the development server to compile the above bundles,
and enables live and hot reloading for frontend source files.

## Testing

Expand Down
56 changes: 56 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 16 additions & 4 deletions packages/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ program
+ 'there are changes to the source files (if needed), building others when navigated to')
.option('-p, --port <port>', 'port for server to listen on (Default is 8080)')
.option('-s, --site-config <file>', 'specify the site config file (default: site.json)')
.option('-d, --dev', 'development mode, enabling live & hot reload for frontend source files.')
.action((userSpecifiedRoot, options) => {
let rootFolder;
try {
Expand All @@ -122,7 +123,8 @@ program
let onePagePath = options.onePage === true ? INDEX_MARKDOWN_FILE : options.onePage;
onePagePath = onePagePath ? utils.ensurePosix(onePagePath) : onePagePath;

const site = new Site(rootFolder, outputFolder, onePagePath, options.forceReload, options.siteConfig);
const site = new Site(rootFolder, outputFolder, onePagePath,
options.forceReload, options.siteConfig, options.dev);

const addHandler = (filePath) => {
logger.info(`[${new Date().toLocaleTimeString()}] Reload for file add: ${filePath}`);
Expand Down Expand Up @@ -160,11 +162,9 @@ program
});
};

const onePageHtmlUrl = onePagePath && `/${onePagePath.replace(/\.(md|mbd|mbdf)$/, '.html')}`;

// server config
const serverConfig = {
open: options.open && (onePageHtmlUrl || true),
open: options.open,
logLevel: 0,
root: outputFolder,
port: options.port || 8080,
Expand All @@ -179,6 +179,13 @@ program
.then((config) => {
serverConfig.mount.push([config.baseUrl || '/', outputFolder]);

if (options.dev) {
// eslint-disable-next-line global-require
const getMiddlewares = require('@markbind/core-web/webpack.dev');
getMiddlewares(`${config.baseUrl}/markbind`)
.forEach(middleware => serverConfig.middleware.push(middleware));
}

if (onePagePath) {
const lazyReloadMiddleware = function (req, res, next) {
const urlExtension = path.posix.extname(req.url);
Expand Down Expand Up @@ -216,7 +223,12 @@ program
next();
};

const onePageHtmlUrl = `${config.baseUrl}/${onePagePath.replace(/\.(md|mbd|mbdf)$/, '.html')}`;
serverConfig.open = serverConfig.open && onePageHtmlUrl;

serverConfig.middleware.push(lazyReloadMiddleware);
} else {
serverConfig.open = serverConfig.open && `${config.baseUrl}/`;
}

return site.generate();
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@markbind/core": "2.15.2",
"@markbind/core-web": "2.15.2",
"bluebird": "^3.7.2",
"chalk": "^3.0.0",
"cheerio": "^0.22.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/core-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"vue-loader": "^15.9.3",
"webpack": "^4.44.0",
"webpack-cli": "^3.3.12",
"webpack-dev-middleware": "^3.7.2",
"webpack-hot-middleware": "^2.25.0",
"webpack-merge": "^5.0.9"
},
"publishConfig": {
Expand Down
54 changes: 39 additions & 15 deletions packages/core-web/webpack.dev.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');

const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

const config = require('./webpack.common.js');

module.exports = merge(config, {
mode: 'development',
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader',
],
},
module.exports = (publicPath) => {
const webpackDevConfig = merge(config, {
mode: 'development',
entry: {
markbind: ['webpack-hot-middleware/client', path.join(__dirname, 'src', 'index.js')],
},
output: {
publicPath,
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader',
],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new VueLoaderPlugin(),
],
},
plugins: [new VueLoaderPlugin()],
});
});

const compiler = webpack(webpackDevConfig);
return [
webpackDevMiddleware(compiler, {
publicPath,
}),
webpackHotMiddleware(compiler),
];
};
5 changes: 5 additions & 0 deletions packages/core/src/Page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class Page {
* @param {PageConfig} pageConfig
*/
constructor(pageConfig) {
/**
* @type {boolean}
*/
this.dev = pageConfig.dev;
/**
* @type {Object<string, any>}
*/
Expand Down Expand Up @@ -309,6 +313,7 @@ class Page {
asset,
baseUrl: this.baseUrl,
content: this.content,
dev: this.dev,
faviconUrl: this.faviconUrl,
footerHtml: this.pageSectionsHtml.footer || '',
headerHtml: this.pageSectionsHtml.header || '',
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Page/page.njk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<link rel="stylesheet" href="{{ asset.glyphicons }}">
<link rel="stylesheet" href="{{ asset.octicons }}">
<link rel="stylesheet" href="{{ asset.highlight }}">
<link rel="stylesheet" href="{{ asset.markBindCss }}">
{%- if not dev -%}<link rel="stylesheet" href="{{ asset.markBindCss }}">{%- endif -%}
<script src="{{ asset.polyfillJs }}"></script>
<script src="{{ asset.vue }}"></script>
<script src="{{ asset.markBindJs }}"></script>
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/Site/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ const TOP_NAV_DEFAULT = '<header><navbar placement="top" type="inverse">\n'
const MARKBIND_LINK_HTML = `<a href='${MARKBIND_WEBSITE_URL}'>MarkBind ${MARKBIND_VERSION}</a>`;

class Site {
constructor(rootPath, outputPath, onePagePath, forceReload = false, siteConfigPath = SITE_CONFIG_NAME) {
constructor(rootPath, outputPath, onePagePath, forceReload = false,
siteConfigPath = SITE_CONFIG_NAME, dev) {
this.dev = !!dev;

this.rootPath = rootPath;
this.outputPath = outputPath;
this.tempPath = path.join(rootPath, TEMP_FOLDER_NAME);
Expand Down Expand Up @@ -255,6 +258,7 @@ class Site {
const sourcePath = path.join(this.rootPath, config.pageSrc);
const resultPath = path.join(this.outputPath, Site.setExtension(config.pageSrc, '.html'));
return new Page({
dev: this.dev,
baseUrl: this.siteConfig.baseUrl,
baseUrlMap: this.baseUrlMap,
content: '',
Expand Down

0 comments on commit 07c364e

Please sign in to comment.