2
0
mirror of https://github.com/frappe/books.git synced 2025-02-02 20:18:26 +00:00

Frappe CLI for development

- frappe start command
  - starts frappejs server
  - sets up webpack middleware to bundle files
- introduce frappe.conf.js for configuration
This commit is contained in:
Faris Ansari 2018-07-29 16:51:03 +05:30
parent cf00d26c91
commit ddd9a786d2
9 changed files with 2898 additions and 38 deletions

12
cli.js
View File

@ -2,9 +2,19 @@
const program = require('commander');
const process = require('process');
const package = require('./package.json');
const boilerplate = require('frappejs/model/boilerplate');
program.command('new-model <name>')
program
.version(package.version)
program
.command('start [mode]')
.description('Start development server')
.action(require('./webpack/start'))
program
.command('new-model <name>')
.description('Create a new model in the `models/doctype` folder')
.action((name) => {
boilerplate.make_model_files(name);

View File

@ -3,24 +3,35 @@
"version": "0.0.8",
"description": "Frappe.js",
"main": "index.js",
"bin": "cli.js",
"bin": {
"frappe": "cli.js"
},
"scripts": {
"test": "mocha tests",
"start": "nodemon app.js"
},
"dependencies": {
"awesomplete": "^1.1.2",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"bcrypt": "^2.0.1",
"body-parser": "^1.18.2",
"bootstrap": "^4.1.2",
"case-sensitive-paths-webpack-plugin": "^2.1.2",
"codemirror": "^5.35.0",
"commander": "^2.13.0",
"cors": "^2.8.4",
"css-loader": "^1.0.0",
"deepmerge": "^2.1.0",
"electron": "2.0.5",
"electron-debug": "^2.0.0",
"electron-devtools-installer": "^2.2.4",
"express": "^4.16.2",
"feather-icons": "^4.7.3",
"flatpickr": "^4.3.2",
"frappe-datatable": "^1.1.2",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"jquery": "^3.3.1",
"jwt-simple": "^0.5.1",
"luxon": "^1.0.0",
@ -34,12 +45,18 @@
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"puppeteer": "^1.2.0",
"sass-loader": "^7.0.3",
"showdown": "^1.8.6",
"socket.io": "^2.0.4",
"sqlite3": "^3.1.13",
"vue": "^2.5.16",
"vue-flatpickr-component": "^7.0.4",
"vue-router": "^3.0.1"
"vue-loader": "^15.2.6",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.16.1",
"webpack-dev-server": "^3.1.4",
"webpack-hot-middleware": "^2.22.3"
},
"repository": {
"type": "git",

View File

@ -15,7 +15,10 @@ const bodyParser = require('body-parser');
const fs = require('fs');
const { setupExpressRoute: setRouteForPDF } = require('frappejs/server/pdf');
const auth = require('./../auth/auth')();
const morgan = require('morgan')
const morgan = require('morgan');
const { addWebpackMiddleware } = require('../webpack/serve');
const { getAppConfig } = require('../webpack/utils');
const appConfig = getAppConfig();
require.extensions['.html'] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
@ -35,7 +38,6 @@ module.exports = {
// app
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(staticPath));
app.use(morgan('tiny'));
if (connectionParams.enableCORS) {
@ -53,7 +55,12 @@ module.exports = {
// routes
restAPI.setup(app);
frappe.config.port = connectionParams.port || 8000;
if (process.env.NODE_ENV === 'development') {
// webpack dev server
addWebpackMiddleware(app);
}
frappe.config.port = appConfig.dev.devServerPort
// listen
server.listen(frappe.config.port, () => {

114
webpack/config.js Normal file
View File

@ -0,0 +1,114 @@
const path = require('path');
const webpack = require('webpack');
const { getAppConfig, resolveAppDir } = require('./utils');
const plugins = {
NamedModules: webpack.NamedModulesPlugin,
HotModuleReplacement: webpack.HotModuleReplacementPlugin,
Define: webpack.DefinePlugin,
Progress: webpack.ProgressPlugin,
VueLoader: require('vue-loader/lib/plugin'),
Html: require('html-webpack-plugin'),
CaseSensitivePaths: require('case-sensitive-paths-webpack-plugin'),
FriendlyErrors: require('friendly-errors-webpack-plugin'),
}
const appConfig = getAppConfig();
const isProduction = process.env.NODE_ENV === 'production';
function getConfig() {
const config = {
mode: isProduction ? 'production' : 'development',
context: resolveAppDir(),
entry: appConfig.dev.entry,
output: {
path: path.resolve(appConfig.dev.outputDir),
filename: '[name].js',
publicPath: appConfig.dev.assetsPublicPath
},
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
)
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
}
]
},
resolve: {
extensions: ['.js', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'deepmerge$': 'deepmerge/dist/umd.js'
}
},
plugins: [
new plugins.Define({
'process.env': appConfig.dev.env
}),
new plugins.VueLoader(),
new plugins.Html({
template: resolveAppDir('src/index.html')
}),
new plugins.CaseSensitivePaths(),
new plugins.NamedModules(),
new plugins.HotModuleReplacement(),
new plugins.FriendlyErrors({
compilationSuccessInfo: {
messages: [`FrappeJS server started at http://${appConfig.dev.devServerHost}:${appConfig.dev.devServerPort}`],
},
}),
new plugins.Progress()
],
optimization: {
noEmitOnErrors: false
},
devServer: {
contentBase: './dist',
hot: true,
quiet: true
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// process is injected via DefinePlugin, although some 3rd party
// libraries may require a mock to work properly (#934)
process: 'mock',
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
return config;
}
module.exports = getConfig;

19
webpack/logger.js Normal file
View File

@ -0,0 +1,19 @@
const ms = require('ms');
const chalk = require('chalk');
let prevTime;
module.exports = function (banner, color = 'green') {
return function (message) {
const currentTime = +new Date();
const diff = currentTime - (prevTime || currentTime);
prevTime = currentTime;
if (message) {
console.log(` ${chalk[color](banner)} ${message} ${chalk.green(`+${ms(diff)}`)}`)
}
else {
console.log()
}
}
}

74
webpack/serve.js Normal file
View File

@ -0,0 +1,74 @@
const webpack = require('webpack');
const webpackDevServer = require('webpack-dev-server');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const logger = require('./logger');
const { getAppConfig, resolveAppDir } = require('./utils');
const getWebpackConfig = require('./config');
const log = logger('serve');
const warn = logger('serve', 'red');
const appConfig = getAppConfig();
const webpackConfig = getWebpackConfig();
function addWebpackMiddleware(app) {
log();
log('Starting dev server...');
addWebpackEntryPoints(webpackConfig);
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
logLevel: 'silent',
publicPath: webpackConfig.output.publicPath
}));
app.use(webpackHotMiddleware(compiler, {
path: '/__webpack_hmr'
}));
}
function startWebpackDevServer() {
log();
log('Starting dev server...');
return new Promise(resolve => {
addWebpackEntryPoints(webpackConfig, true);
const compiler = webpack(webpackConfig);
const server = new webpackDevServer(compiler, webpackConfig.devServer);
const { devServerHost, devServerPort } = appConfig.dev;
server.listen(devServerPort, devServerHost, () => {
// listening on devServerPort
compiler.hooks.done.tap('webpack done compiling', function() {
resolve(server);
});
});
})
}
function addWebpackEntryPoints(webpackConfig, forDevServer) {
const devServerEntryPoints = [
resolveAppDir('node_modules/webpack-dev-server/client/index.js') + '?http://localhost',
'webpack/hot/dev-server'
];
const middlewareEntryPoints = [
'webpack-hot-middleware/client?path=/__webpack_hmr'
];
const entryPoints = forDevServer ? devServerEntryPoints : middlewareEntryPoints;
const entry = webpackConfig.entry;
Object.keys(entry).forEach(key => {
entry[key] = [...entryPoints, entry[key]];
});
}
module.exports = {
addWebpackMiddleware,
startWebpackDevServer
}

25
webpack/start.js Normal file
View File

@ -0,0 +1,25 @@
const { spawn } = require('child_process');
const { startWebpackDevServer } = require('./serve');
const { getAppConfig, resolveAppDir } = require('./utils');
const appConfig = getAppConfig();
module.exports = function start(mode) {
process.env.NODE_ENV = 'development';
if (mode === 'electron') {
const electron = require('electron');
const electronPaths = appConfig.electron.paths;
startWebpackDevServer()
.then((devServer) => {
const p = spawn(electron, [resolveAppDir(electronPaths.mainDev)], { stdio: 'inherit' })
p.on('close', () => {
devServer.close();
});
});
} else {
const nodePaths = appConfig.node.paths;
spawn('node', [resolveAppDir(nodePaths.main)], { stdio: 'inherit' })
}
}

44
webpack/utils.js Normal file
View File

@ -0,0 +1,44 @@
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const deepmerge = require('deepmerge');
const logger = require('./logger');
const frappeConf = 'frappe.conf.js';
function getAppDir() {
let dir = process.cwd();
if (fs.existsSync(path.join(dir, frappeConf))) {
return dir;
}
warn = logger('utils', 'red')
warn();
warn(`Looks like this is not the root of a FrappeJS project`);
warn(`Please run this command from a folder which contains ${chalk.yellow(frappeConf)} file`);
warn();
process.exit(1);
}
function getAppConfig() {
const defaults = {
dev: {
devServerHost: 'localhost',
devServerPort: 8000
}
}
const appConfig = require(path.resolve(getAppDir(), frappeConf));
return deepmerge(defaults, appConfig);
}
function resolveAppDir(...args) {
return path.resolve(getAppDir(), ...args);
}
module.exports = {
getAppDir,
getAppConfig,
resolveAppDir
}

2614
yarn.lock

File diff suppressed because it is too large Load Diff