@@ -56,26 +56,13 @@ | |||||
"vue-router": "^2.0.0" | "vue-router": "^2.0.0" | ||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"babel-runtime": "^6.26.0", | |||||
"chalk": "^2.3.2", | "chalk": "^2.3.2", | ||||
"esbuild": "^0.11.11", | "esbuild": "^0.11.11", | ||||
"esbuild-plugin-postcss2": "^0.0.9", | "esbuild-plugin-postcss2": "^0.0.9", | ||||
"esbuild-vue": "^0.2.0", | "esbuild-vue": "^0.2.0", | ||||
"fast-glob": "^3.2.5", | "fast-glob": "^3.2.5", | ||||
"graphlib": "^2.1.8", | |||||
"http-proxy": "^1.18.1", | |||||
"launch-editor": "^2.2.1", | "launch-editor": "^2.2.1", | ||||
"less": "^3.11.1", | |||||
"md5": "^2.3.0", | "md5": "^2.3.0", | ||||
"rollup": "^1.2.2", | |||||
"rollup-plugin-buble": "^0.19.2", | |||||
"rollup-plugin-commonjs": "^8.3.0", | |||||
"rollup-plugin-multi-entry": "^2.0.2", | |||||
"rollup-plugin-node-resolve": "^4.0.1", | |||||
"rollup-plugin-postcss": "^2.0.3", | |||||
"rollup-plugin-terser": "^4.0.4", | |||||
"rollup-plugin-vue": "4.2.0", | |||||
"svg-sprite": "^1.5.0", | |||||
"yargs": "^16.2.0" | "yargs": "^16.2.0" | ||||
}, | }, | ||||
"snyk": true | "snyk": true | ||||
@@ -1,179 +0,0 @@ | |||||
const fs = require('fs'); | |||||
const path = require('path'); | |||||
const chalk = require('chalk'); | |||||
const rollup = require('rollup'); | |||||
const { execSync } = require('child_process'); | |||||
const log = console.log; // eslint-disable-line | |||||
const { | |||||
get_build_json, | |||||
get_app_path, | |||||
apps_list, | |||||
run_serially, | |||||
assets_path, | |||||
sites_path | |||||
} = require('./rollup.utils'); | |||||
const { | |||||
get_options_for, | |||||
get_options | |||||
} = require('./config'); | |||||
const skip_frappe = process.argv.includes("--skip_frappe") | |||||
if (skip_frappe) { | |||||
let idx = apps_list.indexOf("frappe"); | |||||
if (idx > -1) { | |||||
apps_list.splice(idx, 1); | |||||
} | |||||
} | |||||
const exists = (flag) => process.argv.indexOf(flag) != -1 | |||||
const value = (flag) => (process.argv.indexOf(flag) != -1) ? process.argv[process.argv.indexOf(flag) + 1] : null; | |||||
const files = exists("--files") ? value("--files").split(",") : false; | |||||
const build_for_app = exists("--app") ? value("--app") : null; | |||||
const concat = !exists("--no-concat"); | |||||
if (!files) show_production_message(); | |||||
ensure_js_css_dirs(); | |||||
if (concat) concatenate_files(); | |||||
create_build_file(); | |||||
if (files) { | |||||
build_files(files); | |||||
} else if (build_for_app) { | |||||
build_assets_for_app(build_for_app) | |||||
.then(() => { | |||||
run_build_command_for_app(build_for_app); | |||||
}) | |||||
} else { | |||||
build_assets_for_all_apps() | |||||
.then(() => { | |||||
run_build_command_for_apps() | |||||
}); | |||||
} | |||||
function build_assets_for_all_apps() { | |||||
return run_serially( | |||||
apps_list.map(app => () => build_assets(app)) | |||||
); | |||||
} | |||||
function build_assets_for_app(app) { | |||||
return build_assets(app) | |||||
} | |||||
function build_from_(options) { | |||||
const promises = options.map(({ inputOptions, outputOptions, output_file}) => { | |||||
return build(inputOptions, outputOptions) | |||||
.then(() => { | |||||
log(`${chalk.green('✔')} Built ${output_file}`); | |||||
}); | |||||
}); | |||||
const start = Date.now(); | |||||
return Promise.all(promises) | |||||
.then(() => { | |||||
const time = Date.now() - start; | |||||
log(chalk.green(`✨ Done in ${time / 1000}s`)); | |||||
}); | |||||
} | |||||
function build_assets(app) { | |||||
const options = get_options_for(app); | |||||
if (!options.length) return Promise.resolve(); | |||||
log(chalk.yellow(`\nBuilding ${app} assets...\n`)); | |||||
return build_from_(options); | |||||
} | |||||
function build_files(files, app="frappe") { | |||||
let ret; | |||||
for (let file of files) { | |||||
let options = get_options(file, app); | |||||
if (!options.length) return Promise.resolve(); | |||||
ret += build_from_(options); | |||||
} | |||||
return ret; | |||||
} | |||||
function build(inputOptions, outputOptions) { | |||||
return rollup.rollup(inputOptions) | |||||
.then(bundle => bundle.write(outputOptions)) | |||||
.catch(err => { | |||||
log(chalk.red(err)); | |||||
// Kill process to fail in a CI environment | |||||
if (process.env.CI) { | |||||
process.kill(process.pid) | |||||
} | |||||
}); | |||||
} | |||||
function concatenate_files() { | |||||
// only concatenates files, not processed through rollup | |||||
const files_to_concat = Object.keys(get_build_json('frappe')) | |||||
.filter(filename => filename.startsWith('concat:')); | |||||
files_to_concat.forEach(output_file => { | |||||
const input_files = get_build_json('frappe')[output_file]; | |||||
const file_content = input_files.map(file_name => { | |||||
let prefix = get_app_path('frappe'); | |||||
if (file_name.startsWith('node_modules/')) { | |||||
prefix = path.resolve(get_app_path('frappe'), '..'); | |||||
} | |||||
const full_path = path.resolve(prefix, file_name); | |||||
return `/* ${file_name} */\n` + fs.readFileSync(full_path); | |||||
}).join('\n\n'); | |||||
const output_file_path = output_file.slice('concat:'.length); | |||||
const target_path = path.resolve(assets_path, output_file_path); | |||||
fs.writeFileSync(target_path, file_content); | |||||
log(`${chalk.green('✔')} Built ${output_file_path}`); | |||||
}); | |||||
} | |||||
function create_build_file() { | |||||
const touch = require('touch'); | |||||
touch(path.join(sites_path, '.build'), { force: true }); | |||||
} | |||||
function run_build_command_for_apps() { | |||||
let cwd = process.cwd(); | |||||
apps_list.map(app => run_build_command_for_app(app)) | |||||
process.chdir(cwd); | |||||
} | |||||
function run_build_command_for_app(app) { | |||||
if (app === 'frappe') return; | |||||
let root_app_path = path.resolve(get_app_path(app), '..'); | |||||
let package_json = path.resolve(root_app_path, 'package.json'); | |||||
if (fs.existsSync(package_json)) { | |||||
let package = require(package_json); | |||||
if (package.scripts && package.scripts.build) { | |||||
console.log('\nRunning build command for', chalk.bold(app)); | |||||
process.chdir(root_app_path); | |||||
execSync('yarn build', { encoding: 'utf8', stdio: 'inherit' }); | |||||
} | |||||
} | |||||
} | |||||
function ensure_js_css_dirs() { | |||||
const paths = [ | |||||
path.resolve(assets_path, 'js'), | |||||
path.resolve(assets_path, 'css') | |||||
]; | |||||
paths.forEach(path => { | |||||
if (!fs.existsSync(path)) { | |||||
fs.mkdirSync(path); | |||||
} | |||||
}); | |||||
} | |||||
function show_production_message() { | |||||
const production = process.env.FRAPPE_ENV === 'production'; | |||||
log(chalk.yellow(`${production ? 'Production' : 'Development'} mode`)); | |||||
} |
@@ -1,239 +0,0 @@ | |||||
const path = require('path'); | |||||
const fs = require('fs'); | |||||
const chalk = require('chalk'); | |||||
const log = console.log; // eslint-disable-line | |||||
const multi_entry = require('rollup-plugin-multi-entry'); | |||||
const commonjs = require('rollup-plugin-commonjs'); | |||||
const node_resolve = require('rollup-plugin-node-resolve'); | |||||
const postcss = require('rollup-plugin-postcss'); | |||||
const buble = require('rollup-plugin-buble'); | |||||
const { terser } = require('rollup-plugin-terser'); | |||||
const vue = require('rollup-plugin-vue'); | |||||
const frappe_html = require('./frappe-html-plugin'); | |||||
const less_loader = require('./less-loader'); | |||||
const production = process.env.FRAPPE_ENV === 'production'; | |||||
const { | |||||
apps_list, | |||||
assets_path, | |||||
bench_path, | |||||
get_public_path, | |||||
get_app_path, | |||||
get_build_json, | |||||
get_options_for_scss | |||||
} = require('./rollup.utils'); | |||||
function get_rollup_options(output_file, input_files) { | |||||
if (output_file.endsWith('.js')) { | |||||
return get_rollup_options_for_js(output_file, input_files); | |||||
} else if(output_file.endsWith('.css')) { | |||||
return get_rollup_options_for_css(output_file, input_files); | |||||
} | |||||
} | |||||
function get_rollup_options_for_js(output_file, input_files) { | |||||
const node_resolve_paths = [].concat( | |||||
// node_modules of apps directly importable | |||||
apps_list.map(app => path.resolve(get_app_path(app), '../node_modules')).filter(fs.existsSync), | |||||
// import js file of any app if you provide the full path | |||||
apps_list.map(app => path.resolve(get_app_path(app), '..')).filter(fs.existsSync) | |||||
); | |||||
const plugins = [ | |||||
// enables array of inputs | |||||
multi_entry(), | |||||
// .html -> .js | |||||
frappe_html(), | |||||
// ignore css imports | |||||
ignore_css(), | |||||
// .vue -> .js | |||||
vue.default(), | |||||
// ES6 -> ES5 | |||||
buble({ | |||||
objectAssign: 'Object.assign', | |||||
transforms: { | |||||
dangerousForOf: true, | |||||
classes: false | |||||
}, | |||||
exclude: [path.resolve(bench_path, '**/*.css'), path.resolve(bench_path, '**/*.less')] | |||||
}), | |||||
commonjs(), | |||||
node_resolve({ | |||||
customResolveOptions: { | |||||
paths: node_resolve_paths | |||||
} | |||||
}), | |||||
production && terser() | |||||
]; | |||||
return { | |||||
inputOptions: { | |||||
input: input_files, | |||||
plugins: plugins, | |||||
context: 'window', | |||||
external: ['jquery'], | |||||
onwarn({ code, message, loc, frame }) { | |||||
// skip warnings | |||||
if (['EVAL', 'SOURCEMAP_BROKEN', 'NAMESPACE_CONFLICT'].includes(code)) return; | |||||
if ('UNRESOLVED_IMPORT' === code) { | |||||
log(chalk.yellow.underline(code), ':', message); | |||||
const command = chalk.yellow('bench setup requirements'); | |||||
log(`Cannot find some dependencies. You may have to run "${command}" to install them.`); | |||||
log(); | |||||
return; | |||||
} | |||||
if (loc) { | |||||
log(`${loc.file} (${loc.line}:${loc.column}) ${message}`); | |||||
if (frame) log(frame); | |||||
} else { | |||||
log(chalk.yellow.underline(code), ':', message); | |||||
} | |||||
} | |||||
}, | |||||
outputOptions: { | |||||
file: path.resolve(assets_path, output_file), | |||||
format: 'iife', | |||||
name: 'Rollup', | |||||
globals: { | |||||
'jquery': 'window.jQuery' | |||||
}, | |||||
sourcemap: true | |||||
} | |||||
}; | |||||
} | |||||
function get_rollup_options_for_css(output_file, input_files) { | |||||
const output_path = path.resolve(assets_path, output_file); | |||||
const starts_with_css = output_file.startsWith('css/'); | |||||
const plugins = [ | |||||
// enables array of inputs | |||||
multi_entry(), | |||||
// less -> css | |||||
postcss({ | |||||
plugins: [ | |||||
starts_with_css ? require('autoprefixer')() : null, | |||||
starts_with_css && production ? require('cssnano')({ preset: 'default' }) : null | |||||
].filter(Boolean), | |||||
extract: output_path, | |||||
loaders: [less_loader], | |||||
use: [ | |||||
['less', { | |||||
// import other less/css files starting from these folders | |||||
paths: [ | |||||
path.resolve(get_public_path('frappe'), 'less') | |||||
] | |||||
}], | |||||
['sass', { | |||||
...get_options_for_scss(), | |||||
outFile: output_path, | |||||
sourceMapContents: true | |||||
}] | |||||
], | |||||
include: [ | |||||
path.resolve(bench_path, '**/*.less'), | |||||
path.resolve(bench_path, '**/*.scss'), | |||||
path.resolve(bench_path, '**/*.css') | |||||
], | |||||
sourceMap: starts_with_css && !production | |||||
}) | |||||
]; | |||||
return { | |||||
inputOptions: { | |||||
input: input_files, | |||||
plugins: plugins, | |||||
onwarn(warning) { | |||||
// skip warnings | |||||
if (['EMPTY_BUNDLE'].includes(warning.code)) return; | |||||
// console.warn everything else | |||||
log(chalk.yellow.underline(warning.code), ':', warning.message); | |||||
log(warning); | |||||
} | |||||
}, | |||||
outputOptions: { | |||||
// this file is always empty, remove it later? | |||||
file: path.resolve(assets_path, `css/rollup.manifest.css`), | |||||
format: 'cjs' | |||||
} | |||||
}; | |||||
} | |||||
function get_options(file, app="frappe") { | |||||
const build_json = get_build_json(app); | |||||
if (!build_json) return []; | |||||
return Object.keys(build_json) | |||||
.map(output_file => { | |||||
if (output_file === file) { | |||||
if (output_file.startsWith('concat:')) return null; | |||||
const input_files = build_json[output_file] | |||||
.map(input_file => { | |||||
let prefix = get_app_path(app); | |||||
if (input_file.startsWith('node_modules/')) { | |||||
prefix = path.resolve(get_app_path(app), '..'); | |||||
} | |||||
return path.resolve(prefix, input_file); | |||||
}); | |||||
return Object.assign( | |||||
get_rollup_options(output_file, input_files), { | |||||
output_file | |||||
}); | |||||
} | |||||
}) | |||||
.filter(Boolean); | |||||
} | |||||
function get_options_for(app) { | |||||
const build_json = get_build_json(app); | |||||
if (!build_json) return []; | |||||
return Object.keys(build_json) | |||||
.map(output_file => { | |||||
if (output_file.startsWith('concat:')) return null; | |||||
let files = build_json[output_file]; | |||||
if (typeof files === 'string') { | |||||
files = [files]; | |||||
} | |||||
const input_files = files | |||||
.map(input_file => { | |||||
let prefix = get_app_path(app); | |||||
if (input_file.startsWith('node_modules/')) { | |||||
prefix = path.resolve(get_app_path(app), '..'); | |||||
} | |||||
return path.resolve(prefix, input_file); | |||||
}); | |||||
return Object.assign( | |||||
get_rollup_options(output_file, input_files), { | |||||
output_file | |||||
}); | |||||
}) | |||||
.filter(Boolean); | |||||
} | |||||
function ignore_css() { | |||||
return { | |||||
name: 'ignore-css', | |||||
transform(code, id) { | |||||
if (!['.css', '.scss', '.sass', '.less'].some(ext => id.endsWith(ext))) { | |||||
return null; | |||||
} | |||||
return ` | |||||
// ignored ${id} | |||||
`; | |||||
} | |||||
}; | |||||
}; | |||||
module.exports = { | |||||
get_options_for, | |||||
get_options | |||||
}; |
@@ -1,26 +0,0 @@ | |||||
const path = require('path'); | |||||
function scrub_html_template(content) { | |||||
content = content.replace(/\s/g, ' '); | |||||
content = content.replace(/(<!--.*?-->)/g, ''); | |||||
return content.replace("'", "\'"); // eslint-disable-line | |||||
} | |||||
module.exports = function frappe_html() { | |||||
return { | |||||
name: 'frappe-html', | |||||
transform(code, id) { | |||||
if (!id.endsWith('.html')) return null; | |||||
var filepath = path.basename(id).split('.'); | |||||
filepath.splice(-1); | |||||
var key = filepath.join("."); | |||||
var content = scrub_html_template(code); | |||||
return ` | |||||
frappe.templates['${key}'] = '${content}'; | |||||
`; | |||||
} | |||||
}; | |||||
}; |
@@ -1,54 +0,0 @@ | |||||
const pify = require('pify'); | |||||
const importCwd = require('import-cwd'); | |||||
const path = require('path'); | |||||
const getFileName = filepath => path.basename(filepath); | |||||
function loadModule(moduleId) { | |||||
// Trying to load module normally (relative to plugin directory) | |||||
try { | |||||
return require(moduleId); | |||||
} catch (_) { | |||||
// Ignore error | |||||
} | |||||
// Then, trying to load it relative to CWD | |||||
return importCwd.silent(moduleId); | |||||
} | |||||
module.exports = { | |||||
name: 'less', | |||||
test: /\.less$/, | |||||
async process({ | |||||
code | |||||
}) { | |||||
const less = loadModule('less'); | |||||
if (!less) { | |||||
throw new Error('You need to install "less" packages in order to process Less files'); | |||||
} | |||||
let { | |||||
css, | |||||
map, | |||||
imports | |||||
} = await pify(less.render.bind(less))(code, { | |||||
...this.options, | |||||
sourceMap: this.sourceMap && { outputSourceFiles: true }, | |||||
filename: this.id | |||||
}); | |||||
for (const dep of imports) { | |||||
this.dependencies.add(dep); | |||||
} | |||||
if (map) { | |||||
map = JSON.parse(map); | |||||
map.sources = map.sources.map(source => getFileName(source)); | |||||
} | |||||
return { | |||||
code: css, | |||||
map | |||||
}; | |||||
} | |||||
}; |
@@ -1,97 +0,0 @@ | |||||
const path = require('path'); | |||||
const fs = require('fs'); | |||||
const frappe_path = process.cwd(); | |||||
const bench_path = path.resolve(frappe_path, '..', '..'); | |||||
const sites_path = path.resolve(bench_path, 'sites'); | |||||
const apps_list = | |||||
fs.readFileSync( | |||||
path.resolve(sites_path, 'apps.txt'), { encoding: 'utf-8' } | |||||
).split('\n').filter(Boolean); | |||||
const assets_path = path.resolve(sites_path, 'assets'); | |||||
const app_paths = apps_list.reduce((out, app) => { | |||||
out[app] = path.resolve(bench_path, 'apps', app, app) | |||||
return out; | |||||
}, {}); | |||||
const public_paths = apps_list.reduce((out, app) => { | |||||
out[app] = path.resolve(app_paths[app], 'public'); | |||||
return out; | |||||
}, {}); | |||||
const public_js_paths = apps_list.reduce((out, app) => { | |||||
out[app] = path.resolve(app_paths[app], 'public/js'); | |||||
return out; | |||||
}, {}); | |||||
const bundle_map = apps_list.reduce((out, app) => { | |||||
const public_js_path = public_js_paths[app]; | |||||
if ( fs.existsSync(public_js_path) ) { | |||||
const all_files = fs.readdirSync(public_js_path); | |||||
const js_files = all_files.filter(file => file.endsWith('.js')); | |||||
for (let js_file of js_files) { | |||||
const filename = path.basename(js_file).split('.')[0]; | |||||
out[path.join(app, 'js', filename)] = path.resolve(public_js_path, js_file); | |||||
} | |||||
} | |||||
return out; | |||||
}, {}); | |||||
const get_public_path = app => public_paths[app]; | |||||
const get_build_json_path = app => path.resolve(get_public_path(app), 'build.json'); | |||||
function get_build_json(app) { | |||||
try { | |||||
return require(get_build_json_path(app)); | |||||
} catch (e) { | |||||
// build.json does not exist | |||||
return null; | |||||
} | |||||
} | |||||
function delete_file(path) { | |||||
if (fs.existsSync(path)) { | |||||
fs.unlinkSync(path); | |||||
} | |||||
} | |||||
function run_serially(tasks) { | |||||
let result = Promise.resolve(); | |||||
tasks.forEach(task => { | |||||
if(task) { | |||||
result = result.then ? result.then(task) : Promise.resolve(); | |||||
} | |||||
}); | |||||
return result; | |||||
} | |||||
const get_app_path = app => app_paths[app]; | |||||
const get_options_for_scss = () => { | |||||
const node_modules_path = path.resolve(get_app_path('frappe'), '..', 'node_modules'); | |||||
const app_paths = apps_list.map(get_app_path).map(app_path => path.resolve(app_path, '..')); | |||||
return { | |||||
includePaths: [ | |||||
node_modules_path, | |||||
...app_paths | |||||
] | |||||
}; | |||||
}; | |||||
module.exports = { | |||||
sites_path, | |||||
bundle_map, | |||||
get_public_path, | |||||
get_build_json_path, | |||||
get_build_json, | |||||
get_app_path, | |||||
apps_list, | |||||
assets_path, | |||||
bench_path, | |||||
delete_file, | |||||
run_serially, | |||||
get_options_for_scss | |||||
}; |
@@ -1,102 +0,0 @@ | |||||
const path = require('path'); | |||||
const chalk = require('chalk'); | |||||
const rollup = require('rollup'); | |||||
const log = console.log; // eslint-disable-line | |||||
const { | |||||
apps_list | |||||
} = require('./rollup.utils'); | |||||
const { | |||||
get_options_for | |||||
} = require('./config'); | |||||
const { get_redis_subscriber } = require('../node_utils'); | |||||
const subscriber = get_redis_subscriber(); | |||||
watch_assets(); | |||||
function watch_assets() { | |||||
let watchOptions = []; | |||||
apps_list.map(app => { | |||||
watchOptions.push(...get_watch_options(app)); | |||||
}); | |||||
log(chalk.green(`\nRollup Watcher Started`)); | |||||
let watcher = rollup.watch(watchOptions); | |||||
watcher.on('event', event => { | |||||
switch(event.code) { | |||||
case 'START': { | |||||
log(chalk.yellow(`\nWatching...`)); | |||||
break; | |||||
} | |||||
case 'BUNDLE_START': { | |||||
const output = event.output[0]; | |||||
if (output.endsWith('.js') || output.endsWith('.vue')) { | |||||
log('Rebuilding', path.basename(event.output[0])); | |||||
} | |||||
break; | |||||
} | |||||
case 'ERROR': { | |||||
log_error(event.error); | |||||
break; | |||||
} | |||||
case 'FATAL': { | |||||
log_error(event.error); | |||||
break; | |||||
} | |||||
default: break; | |||||
} | |||||
}); | |||||
} | |||||
function get_watch_options(app) { | |||||
const options = get_options_for(app); | |||||
return options.map(({ inputOptions, outputOptions, output_file}) => { | |||||
return Object.assign({}, inputOptions, { | |||||
output: outputOptions, | |||||
plugins: [log_css_change({output: output_file})].concat(inputOptions.plugins) | |||||
}); | |||||
}); | |||||
} | |||||
function log_css_change({output}) { | |||||
return { | |||||
name: 'log-css-change', | |||||
generateBundle() { | |||||
if (!output.endsWith('.css')) return null; | |||||
log('Rebuilding', path.basename(output)); | |||||
return null; | |||||
} | |||||
}; | |||||
} | |||||
function log_error(error) { | |||||
log(chalk.yellow('Error in: ' + error.id)); | |||||
if (error.stack) { | |||||
log(chalk.red(error.stack)); | |||||
} else { | |||||
log(chalk.red(error.toString())); | |||||
} | |||||
if (error.frame) { | |||||
log(chalk.red(error.frame)); | |||||
} | |||||
// notify redis which in turns tells socketio to publish this to browser | |||||
const payload = { | |||||
event: 'build_error', | |||||
message: ` | |||||
Error in: ${error.id} | |||||
${error.toString()} | |||||
${error.frame ? error.frame : ''} | |||||
` | |||||
}; | |||||
subscriber.publish('events', JSON.stringify(payload)); | |||||
} |