diff --git a/cmd/cmd.js b/cmd/cmd.js index 53ba06f1b..a154cea63 100644 --- a/cmd/cmd.js +++ b/cmd/cmd.js @@ -1,8 +1,35 @@ const program = require('commander'); const EmbarkController = require('./cmd_controller.js'); const i18n = require('../lib/core/i18n/i18n.js'); +const utils = require('../lib/utils/utils.js'); + let embark = new EmbarkController; +// set PWD to process.cwd() since Windows doesn't have a value for PWD +if (!process.env.PWD) { + process.env.PWD = process.cwd(); +} + +// set the anchor for embark's fs.dappPath() +if (!process.env.DAPP_PATH) { + process.env.DAPP_PATH = process.env.PWD; +} + +// set the anchor for embark's fs.embarkPath() +if (!process.env.EMBARK_PATH) { + process.env.EMBARK_PATH = utils.joinPath(__dirname, '..'); +} + +// NOTE: setting NODE_PATH at runtime won't effect lookup behavior in the +// current process, but will take effect in child processes; this enables +// lookup of *global* embark's own node_modules from within dapp scripts (such +// as an ejected webpack.config.js), making embark's dependencies trasitive +// dependencies of a dapp without the dapp explicitly specifying embark as a +// dependency in the dapp's package.json +process.env.NODE_PATH = utils.joinPath(process.env.EMBARK_PATH, 'node_modules') + + (process.env.NODE_PATH ? require('path').delimiter : '') + + (process.env.NODE_PATH || ''); + class Cmd { constructor() { program.version(embark.version); @@ -18,6 +45,7 @@ class Cmd { this.simulator(); this.test(); this.reset(); + this.ejectWebpack(); this.graph(); this.upload(); this.versionCmd(); @@ -99,6 +127,7 @@ class Cmd { .option('-c, --client [client]', __('Use a specific ethereum client or simulator (supported: %s)', 'geth, testrpc')) .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug') .option('--locale [locale]', __('language to use (default: en)')) + .option('--pipeline [pipeline]', __('webpack config to use (default: production)')) .description(__('deploy and build dapp at ') + 'dist/ (default: development)') .action(function (env, _options) { i18n.setOrDetectLocale(_options.locale); @@ -107,6 +136,7 @@ class Cmd { _options.logLevel = _options.loglevel; // fix casing _options.onlyCompile = _options.contracts; _options.client = _options.client || 'geth'; + _options.webpackConfigName = _options.pipeline || 'production'; embark.build(_options); }); } @@ -123,6 +153,7 @@ class Cmd { .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none')) .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug') .option('--locale [locale]', __('language to use (default: en)')) + .option('--pipeline [pipeline]', __('webpack config to use (default: development)')) .description(__('run dapp (default: %s)', 'development')) .action(function (env, options) { i18n.setOrDetectLocale(options.locale); @@ -135,7 +166,8 @@ class Cmd { runWebserver: !options.noserver, useDashboard: !options.nodashboard, logFile: options.logfile, - logLevel: options.loglevel + logLevel: options.loglevel, + webpackConfigName: options.pipeline || 'development' }); }); } @@ -147,6 +179,7 @@ class Cmd { .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none')) .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug') .option('--locale [locale]', __('language to use (default: en)')) + .option('--pipeline [pipeline]', __('webpack config to use (default: development)')) .description(__('Start the Embark console')) .action(function (env, options) { i18n.setOrDetectLocale(options.locale); @@ -155,7 +188,8 @@ class Cmd { client: options.client || 'geth', locale: options.locale, logFile: options.logfile, - logLevel: options.loglevel + logLevel: options.loglevel, + webpackConfigName: options.pipeline || 'development' }); }); } @@ -276,6 +310,15 @@ class Cmd { }); } + ejectWebpack() { + program + .command('eject-webpack') + .description(__('copy the default webpack config into your dapp for customization')) + .action(function () { + embark.ejectWebpack(); + }); + } + versionCmd() { program .command('version') diff --git a/cmd/cmd_controller.js b/cmd/cmd_controller.js index 43f5523f1..b9627a64a 100644 --- a/cmd/cmd_controller.js +++ b/cmd/cmd_controller.js @@ -5,11 +5,6 @@ require('colors'); let version = require('../package.json').version; -// Set PWD to CWD since Windows doesn't have a value for PWD -if (!process.env.PWD) { - process.env.PWD = process.cwd(); -} - class EmbarkController { constructor(options) { @@ -84,7 +79,8 @@ class EmbarkController { logLevel: options.logLevel, context: self.context, useDashboard: options.useDashboard, - webServerConfig: webServerConfig + webServerConfig: webServerConfig, + webpackConfigName: options.webpackConfigName }); engine.init(); @@ -191,7 +187,8 @@ class EmbarkController { logger: options.logger, config: options.config, plugins: options.plugins, - context: this.context + context: this.context, + webpackConfigName: options.webpackConfigName }); engine.init(); @@ -259,7 +256,8 @@ class EmbarkController { logFile: options.logFile, logLevel: options.logLevel, context: this.context, - ipcRole: 'client' + ipcRole: 'client', + webpackConfigName: options.webpackConfigName }); engine.init(); async.waterfall([ @@ -404,10 +402,33 @@ class EmbarkController { var fs = require('../lib/core/fs.js'); fs.removeSync('./chains.json'); fs.removeSync('.embark/'); + fs.removeSync('node_modules/.cache'); fs.removeSync('dist/'); console.log(__("reset done!").green); } + ejectWebpack() { + var fs = require('../lib/core/fs.js'); + var dappConfig = fs.dappPath('webpack.config.js'); + var embarkConfig = fs.embarkPath('lib/pipeline', 'webpack.config.js'); + let ext = 1; + let dappConfigOld = dappConfig; + while (fs.existsSync(dappConfigOld)) { + dappConfigOld = dappConfig + `.${ext}`; + ext++; + } + if (dappConfigOld !== dappConfig) { + fs.copySync(dappConfig, dappConfigOld); + console.warn(`${dappConfig}`.yellow); + console.warn(__('copied to').dim.yellow); + console.warn(`${dappConfigOld}\n`.yellow); + } + fs.copySync(embarkConfig, dappConfig); + console.log(`${embarkConfig}`.green); + console.log(__('copied to').dim.green); + console.log(`${dappConfig}`.green); + } + upload(options) { this.context = options.context || [constants.contexts.upload, constants.contexts.build]; diff --git a/lib/core/config.js b/lib/core/config.js index 3ca8fda60..a43d4c5dc 100644 --- a/lib/core/config.js +++ b/lib/core/config.js @@ -123,11 +123,11 @@ Config.prototype._updateBlockchainCors = function(){ } let cors = corsParts.join(','); - if(blockchainConfig.rpcCorsDomain === 'auto'){ + if(blockchainConfig.rpcCorsDomain === 'auto'){ if(cors.length) blockchainConfig.rpcCorsDomain = cors; else blockchainConfig.rpcCorsDomain = ''; } - if(blockchainConfig.wsOrigins === 'auto'){ + if(blockchainConfig.wsOrigins === 'auto'){ if(cors.length) blockchainConfig.wsOrigins = cors; else blockchainConfig.wsOrigins = ''; } @@ -169,7 +169,7 @@ Config.prototype._getFileOrOject = function(object, filePath, property) { if (typeof (this.configDir) === 'object') { return this.configDir[property]; } - return this.configDir + filePath; + return utils.joinPath(this.configDir, filePath); }; Config.prototype.loadBlockchainConfigFile = function() { diff --git a/lib/core/engine.js b/lib/core/engine.js index c6857095f..3c54aa368 100644 --- a/lib/core/engine.js +++ b/lib/core/engine.js @@ -18,6 +18,7 @@ class Engine { this.useDashboard = options.useDashboard; this.webServerConfig = options.webServerConfig; this.ipcRole = options.ipcRole; + this.webpackConfigName = options.webpackConfigName; } init(_options) { @@ -102,7 +103,8 @@ class Engine { assetFiles: this.config.assetFiles, events: this.events, logger: this.logger, - plugins: this.plugins + plugins: this.plugins, + webpackConfigName: this.webpackConfigName }); this.events.on('code-generator-ready', function () { self.events.request('code', function (abi, contractsJSON) { diff --git a/lib/core/fs.js b/lib/core/fs.js index 927632c73..5e340e966 100644 --- a/lib/core/fs.js +++ b/lib/core/fs.js @@ -77,13 +77,21 @@ function removeSync() { return fs.removeSync.apply(fs.removeSync, arguments); } -// returns embarks root directory -function embarkPath(fileOrDir) { - return utils.joinPath(__dirname, '/../../', fileOrDir); +function anchoredPath(envAnchor, ...args) { + const anchor = process.env[envAnchor]; + if (!anchor) { + console.error(`process.env.${envAnchor} was not set`.bold.red); + process.exit(1); + } + return utils.joinPath(anchor, ...args); +} + +function embarkPath() { + return anchoredPath('EMBARK_PATH', ...arguments); } function dappPath() { - return utils.joinPath(utils.pwd(), ...arguments); + return anchoredPath('DAPP_PATH', ...arguments); } function createWriteStream() { diff --git a/lib/core/plugins.js b/lib/core/plugins.js index ec8c35987..0aa5accef 100644 --- a/lib/core/plugins.js +++ b/lib/core/plugins.js @@ -1,6 +1,5 @@ const async = require('async'); var Plugin = require('./plugin.js'); -var utils = require('../utils/utils.js'); var fs = require('../core/fs.js'); var Plugins = function(options) { @@ -75,7 +74,7 @@ Plugins.prototype.loadInternalPlugin = function(pluginName, pluginConfig) { }; Plugins.prototype.loadPlugin = function(pluginName, pluginConfig) { - var pluginPath = utils.joinPath(utils.pwd(), 'node_modules', pluginName); + var pluginPath = fs.dappPath('node_modules', pluginName); var plugin = require(pluginPath); var pluginWrapper = new Plugin({ diff --git a/lib/core/processes/processWrapper.js b/lib/core/processes/processWrapper.js index 3b934a106..aa5efdd9d 100644 --- a/lib/core/processes/processWrapper.js +++ b/lib/core/processes/processWrapper.js @@ -5,11 +5,6 @@ process.on('uncaughtException', function(e){ const constants = require('../../constants'); const Events = require('./eventsWrapper'); -// Set PWD to CWD since Windows doesn't have a value for PWD -if (!process.env.PWD) { - process.env.PWD = process.cwd(); -} - class ProcessWrapper { /** diff --git a/lib/pipeline/pipeline.js b/lib/pipeline/pipeline.js index 76f69e427..1f0a742be 100644 --- a/lib/pipeline/pipeline.js +++ b/lib/pipeline/pipeline.js @@ -14,6 +14,7 @@ class Pipeline { this.events = options.events; this.logger = options.logger; this.plugins = options.plugins; + this.webpackConfigName = options.webpackConfigName; this.pipelinePlugins = this.plugins.getPluginsFor('pipeline'); } @@ -30,7 +31,7 @@ class Pipeline { function createPlaceholderPage(next){ self.events.request('embark-building-placeholder', (html) => { fs.mkdirpSync(self.buildDir); // create dist/ folder if not already exists - fs.writeFile(self.buildDir + 'index.html', html, next); + fs.writeFile(utils.joinPath(self.buildDir, 'index.html'), html, next); }); }, function buildTheContracts(next) { @@ -51,7 +52,7 @@ class Pipeline { next(); }, - function writeContracts(next) { + function writeContracts(next) { self.events.request('contracts:list', (_err, contracts) => { // ensure the .embark/contracts directory exists (create if not exists) fs.mkdirp(fs.dappPath(".embark/contracts", ''), (err) => { @@ -75,72 +76,63 @@ class Pipeline { if(idx < contracts.length - 1) importsHelperFile.write(',\n'); // add a comma if we have more contracts to add }); }, function(){ - importsHelperFile.write('\n}'); // close the module.exports = {} + importsHelperFile.write('\n}'); // close the module.exports = {} importsHelperFile.close(next); // close the write stream }); }); }); }, + function runWebpack(next) { + self.logger.info(__(`running webpack with '${self.webpackConfigName}' config...`)); + Object.keys(self.assetFiles) + .filter(key => key.match(/\.js?$/)) + .forEach(key => { + self.logger.info(__("writing file") + " " + (utils.joinPath(self.buildDir, key)).bold.dim); + }); + let built = false; + const webpackProcess = new ProcessLauncher({ + modulePath: utils.joinPath(__dirname, 'webpackProcess.js'), + logger: self.logger, + events: self.events, + exitCallback: function (code) { + if (!built) { + return next(`Webpack build exited with code ${code} before the process finished`); + } + if (code) { + self.logger.error(__('Webpack build process exited with code ', code)); + } + } + }); + webpackProcess.send({action: constants.pipeline.init, options: {webpackConfigName: self.webpackConfigName}}); + webpackProcess.send({action: constants.pipeline.build, assets: self.assetFiles, importsList}); + + webpackProcess.once('result', constants.pipeline.built, (msg) => { + built = true; + webpackProcess.kill(); + return next(msg.error); + }); + }, function assetFileWrite(next) { - async.eachOf(self.assetFiles, function (files, targetFile, cb) { - async.map(files, + async.eachOf( + // assetFileWrite should not process .js files + Object.keys(self.assetFiles) + .filter(key => !key.match(/\.js?$/)) + .reduce((obj, key) => { + obj[key] = self.assetFiles[key]; + return obj; + }, {}), + function (files, targetFile, cb) { + const isDir = targetFile.slice(-1) === '/' || targetFile.slice(-1) === '\\' || targetFile.indexOf('.') === -1; + // if it's not a directory + if (!isDir) { + self.logger.info(__("writing file") + " " + (utils.joinPath(self.buildDir, targetFile)).bold.dim); + } + async.map( + files, function (file, fileCb) { self.logger.trace("reading " + file.filename); - - // Not a JS file - if (file.filename.indexOf('.js') < 0) { - return file.content(function (fileContent) { - self.runPlugins(file, fileContent, fileCb); - }); - } - - // JS files - async.waterfall([ - function runWebpack(next) { - let built = false; - const webpackProcess = new ProcessLauncher({ - modulePath: utils.joinPath(__dirname, 'webpackProcess.js'), - logger: self.logger, - events: self.events, - exitCallback: function (code) { - if (!built) { - return next(`File building of ${file.filename} exited with code ${code} before the process finished`); - } - if (code) { - self.logger(__('File building process exited with code ', code)); - } - } - }); - webpackProcess.send({action: constants.pipeline.init, options: {env: self.env}}); - webpackProcess.send({action: constants.pipeline.build, file, importsList}); - - webpackProcess.once('result', constants.pipeline.built, (msg) => { - built = true; - webpackProcess.kill(); - return next(msg.error); - }); - }, - - function readFile(next) { - fs.readFile('./.embark/' + file.filename, (err, data) => { - if (err) { - return next(err); - } - next(null, data.toString()); - }); - }, - - function runPluginsOnContent(fileContent, next) { - self.runPlugins(file, fileContent, next); - } - - ], function (err, contentFile) { - if (err) { - self.logger.error(err.message || err); - return fileCb(err); - } - - fileCb(null, contentFile); + return file.content(function (fileContent) { + self.runPlugins(file, fileContent, fileCb); }); }, function (err, contentFiles) { @@ -148,22 +140,22 @@ class Pipeline { self.logger.error(__('errors found while generating') + ' ' + targetFile); } let dir = targetFile.split('/').slice(0, -1).join('/'); - self.logger.trace("creating dir " + self.buildDir + dir); - fs.mkdirpSync(self.buildDir + dir); + self.logger.trace("creating dir " + utils.joinPath(self.buildDir, dir)); + fs.mkdirpSync(utils.joinPath(self.buildDir, dir)); // if it's a directory - if (targetFile.slice(-1) === '/' || targetFile.indexOf('.') === -1) { + if (isDir) { let targetDir = targetFile; if (targetDir.slice(-1) !== '/') { targetDir = targetDir + '/'; } - async.each(contentFiles, function (file, mapCb) { + async.each(contentFiles, function (file, eachCb) { let filename = file.filename.replace(file.basedir + '/', ''); - self.logger.info("writing file " + (self.buildDir + targetDir + filename).bold.dim); + self.logger.info("writing file " + (utils.joinPath(self.buildDir, targetDir, filename)).bold.dim); - fs.copy(file.path, self.buildDir + targetDir + filename, {overwrite: true}, mapCb); + fs.copy(file.path, utils.joinPath(self.buildDir, targetDir, filename), {overwrite: true}, eachCb); }, cb); return; } @@ -175,20 +167,20 @@ class Pipeline { return file.content; }).join("\n"); - self.logger.info(__("writing file") + " " + (self.buildDir + targetFile).bold.dim); if(new RegExp(/^index.html?/i).test(targetFile)){ targetFile = targetFile.replace('index', 'index-temp'); placeholderPage = targetFile; } - fs.writeFile(self.buildDir + targetFile, content, cb); + fs.writeFile(utils.joinPath(self.buildDir, targetFile), content, cb); } ); }, - next); + next + ); }, function removePlaceholderPage(next){ - let placeholderFile = self.buildDir + placeholderPage; - fs.access(self.buildDir + placeholderPage, (err) => { + let placeholderFile = utils.joinPath(self.buildDir, placeholderPage); + fs.access(utils.joinPath(self.buildDir, placeholderPage), (err) => { if (err) return next(); // index-temp doesn't exist, do nothing // rename index-temp.htm/l to index.htm/l, effectively replacing our placeholder page @@ -204,7 +196,8 @@ class Pipeline { if (self.pipelinePlugins.length <= 0) { return fileCb(null, {content: fileContent, filename: file.filename, path: file.path, basedir: file.basedir, modified: true}); } - async.eachSeries(self.pipelinePlugins, + async.eachSeries( + self.pipelinePlugins, function(plugin, pluginCB) { if (file.options && file.options.skipPipeline) { return pluginCB(); diff --git a/lib/pipeline/webpack.config.js b/lib/pipeline/webpack.config.js new file mode 100644 index 000000000..db190f496 --- /dev/null +++ b/lib/pipeline/webpack.config.js @@ -0,0 +1,176 @@ +// some packages, plugins, and presets referenced/required in this webpack +// config are deps of embark and will be transitive dapp deps unless specified +// in the dapp's own package.json + +// embark modifies process.env.NODE_PATH so that when running dapp scripts in +// embark's child processes, embark's own node_modules directory will be +// searched by node's require(); however, webpack and babel do not directly +// support NODE_PATH, so modules such as babel plugins and presets must be +// resolved with require.resolve(); that is only necessary if a plugin/preset +// is in embark's node_modules vs. the dapp's node_modules + +const cloneDeep = require('lodash.clonedeep'); +const CompressionPlugin = require('compression-webpack-plugin'); +const glob = require('glob'); +const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); +const path = require('path'); + +const dappPath = process.env.DAPP_PATH; +const embarkPath = process.env.EMBARK_PATH; + +const embarkAliases = require(path.join(dappPath, '.embark/embark-aliases.json')); +const embarkAssets = require(path.join(dappPath, '.embark/embark-assets.json')); +const embarkNodeModules = path.join(embarkPath, 'node_modules'); +const embarkJson = require(path.join(dappPath, 'embark.json')); + +const buildDir = path.join(dappPath, embarkJson.buildDir); + +// it's important to `embark reset` if a pkg version is specified in +// embark.json and changed/removed later, otherwise pkg resolution may behave +// unexpectedly +let versions; +try { + versions = glob.sync(path.join(dappPath, '.embark/versions/*/*')); +} catch (e) { + versions = []; +} + +const entry = Object.keys(embarkAssets) + .filter(key => key.match(/\.js?$/)) + .reduce((obj, key) => { + // webpack entry paths should start with './' if they're relative to the + // webpack context; embark.json "app" keys correspond to lists of .js + // source paths relative to the top-level dapp dir and may be missing the + // leading './' + obj[key] = embarkAssets[key] + .map(file => { + let file_path = file.path; + if (!file.path.match(/^\.\//)) { + file_path = './' + file_path; + } + return file_path; + }); + return obj; + }, {}); + +function resolve(pkgName) { + if (Array.isArray(pkgName)) { + const _pkgName = pkgName[0]; + pkgName[0] = require.resolve(_pkgName); + return pkgName; + } + return require.resolve(pkgName); +} + +// base config +// ----------------------------------------------------------------------------- + +const base = { + context: dappPath, + entry: entry, + module: { + rules: [ + { + test: /\.css$/, + use: [{loader: 'style-loader'}, {loader: 'css-loader'}] + }, + { + test: /\.scss$/, + use: [{loader: 'style-loader'}, {loader: 'css-loader'}] + }, + { + test: /\.(png|woff|woff2|eot|ttf|svg)$/, + loader: 'url-loader?limit=100000' + }, + { + test: /\.jsx?$/, + loader: 'babel-loader', + exclude: /(node_modules|bower_components|\.embark[\\/]versions)/, + options: { + plugins: [ + [ + 'babel-plugin-module-resolver', { + 'alias': embarkAliases + } + ], + [ + '@babel/plugin-transform-runtime', { + corejs: 2, + useESModules: true + } + ] + ].map(resolve), + presets: [ + [ + '@babel/preset-env', { + modules: false, + targets: { + browsers: ['last 1 version', 'not dead', '> 0.2%'] + } + } + ], + '@babel/preset-react' + ].map(resolve) + } + } + ] + }, + output: { + filename: (chunkData) => chunkData.chunk.name, + // globalObject workaround for node-compatible UMD builds with webpack 4 + // see: https://github.com/webpack/webpack/issues/6522#issuecomment-371120689 + globalObject: 'typeof self !== \'undefined\' ? self : this', + libraryTarget: 'umd', + path: buildDir + }, + plugins: [new HardSourceWebpackPlugin()], + // profiling and generating verbose stats increases build time; if stats + // are generated embark will write the output to: + // path.join(dappPath, '.embark/stats.[json,report]') + // to visualize the stats info in a browser run: + // npx webpack-bundle-analyzer .embark/stats.json + profile: true, stats: 'verbose', + resolve: { + alias: embarkAliases, + modules: [ + ...versions, + 'node_modules', + embarkNodeModules + ] + }, + resolveLoader: { + modules: [ + 'node_modules', + embarkNodeModules + ] + } +}; + +// development config +// ----------------------------------------------------------------------------- + +const development = cloneDeep(base); +// full source maps increase build time but are useful during dapp development +development.devtool = 'source-map'; +development.mode = 'development'; +// alternatively: +// development.mode = 'none'; +development.name = 'development'; +const devBabelLoader = development.module.rules[3]; +devBabelLoader.options.compact = false; + +// production config +// ----------------------------------------------------------------------------- + +const production = cloneDeep(base); +production.mode = 'production'; +production.name = 'production'; +production.plugins.push(new CompressionPlugin()); + +// export a list of named configs +// ----------------------------------------------------------------------------- + +module.exports = [ + development, + production +]; diff --git a/lib/pipeline/webpackProcess.js b/lib/pipeline/webpackProcess.js index 32e579fb2..8e75cdc17 100644 --- a/lib/pipeline/webpackProcess.js +++ b/lib/pipeline/webpackProcess.js @@ -1,208 +1,104 @@ -const async = require('async'); -const webpack = require('webpack'); -const utils = require('../utils/utils'); -const fs = require('../core/fs'); const constants = require('../constants'); -const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); +const fs = require('../core/fs'); const ProcessWrapper = require('../core/processes/processWrapper'); -const path = require('path'); -const glob = require('glob'); +const webpack = require('webpack'); +const writeFile = require('util').promisify(require('fs').writeFile); +const {errorMessage} = require('../utils/utils'); let webpackProcess; class WebpackProcess extends ProcessWrapper { constructor(options) { super(options); - this.env = options.env; + this.webpackConfigName = options.webpackConfigName; } - build(file, importsList, callback) { - const self = this; - let realCwd; - - async.waterfall([ - function changeCwd(next) { - realCwd = utils.pwd(); - process.chdir(fs.embarkPath('')); - next(); - }, - - function runWebpack(next) { - self.webpackRun(file.filename, {}, true, importsList, true, realCwd, next); - }, - - function changeCwdBack(next) { - process.chdir(realCwd); - next(); - } - ], (err) => { - process.chdir(realCwd); - callback(err); - }); + async build(assets, importsList, callback) { + try { + await this.webpackRun(assets, importsList, callback); + } catch (e) { + callback(errorMessage(e)); + } } - webpackRun(filename, options, includeModules, importsList, detectErrors, realCwd, callback) { - const self = this; - glob(fs.dappPath('.embark/versions/*/*'), (err, files) => { - let versions; - if (err) { - console.error(err); - versions = []; + async webpackRun(assets, importsList, callback) { + try { + await writeFile( + fs.dappPath('.embark/embark-aliases.json'), + JSON.stringify(importsList) + ); + await writeFile( + fs.dappPath('.embark/embark-assets.json'), + JSON.stringify(assets) + ); + } catch (e) { + return callback(errorMessage(e)); + } + + const dappConfigPath = fs.dappPath('webpack.config.js'); + const defaultConfigPath = fs.embarkPath('lib/pipeline', 'webpack.config.js'); + + let config, configPath; + try { + if (fs.existsSync(dappConfigPath)) { + configPath = dappConfigPath; + delete require.cache[configPath]; } else { - versions = files; + configPath = defaultConfigPath; } - let defaultOptions = { - mode: self.env === 'production' ? 'production' : 'none', - // devtool: self.env === 'development' ? 'source-map' : false, - // pipeline would need to copy .map files to dist/ target dir - // note: generating full source maps ('source-map') roughly doubles build time - entry: fs.dappPath(filename), - output: { - globalObject: 'typeof self !== \'undefined\' ? self : this', - libraryExport: 'default', - libraryTarget: 'umd', - path: fs.dappPath('.embark'), - filename: filename, - umdNamedDefine: true - }, - // profile: true, - // stats: 'verbose', - // note: generating and writing to disk verbose stats increases build time - resolve: { - alias: importsList, - modules: [ - fs.dappPath('node_modules'), - ...versions, - fs.embarkPath('node_modules') - ] - }, - plugins: [ - new HardSourceWebpackPlugin({ - cacheDirectory: fs.dappPath('node_modules/.cache/hard-source'), - // ufglify (wp mode: production) will still save its cache in embark's node_modules/.cache/ - environmentHash: { - root: fs.dappPath() - } - }), - new HardSourceWebpackPlugin.ExcludeModulePlugin( - [{test: /app[\\/]|contracts[\\/]/}] - ) - ] - }; - - let webpackOptions = utils.recursiveMerge(defaultOptions, options); - - if (includeModules) { - webpackOptions.module = { - rules: [ - { - test: /\.css$/, - use: [{loader: "style-loader"}, {loader: "css-loader"}] - }, - { - test: /\.scss$/, - use: [{loader: "style-loader"}, {loader: "css-loader"}] - }, - { - test: /\.(png|woff|woff2|eot|ttf|svg)$/, - loader: 'url-loader?limit=100000' - }, - { - test: /\.js$/, - loader: "babel-loader", - exclude: /(node_modules|bower_components|\.embark[\\/]versions)/, - options: { - presets: [ - [ - "@babel/preset-env", { - modules: false, - targets: { - browsers: ["last 1 version", "not dead", "> 0.2%"] - } - } - ], - "@babel/preset-react" - ].map(pkg => { - if (Array.isArray(pkg)) { - let _pkg = pkg[0]; - pkg[0] = require.resolve(_pkg); - return pkg; - } else { - return require.resolve(pkg); - } - }), - plugins: [ - "babel-plugin-webpack-aliases", - [ - "@babel/plugin-transform-runtime", { - corejs: 2, - useESModules: true - } - ] - ].map(pkg => { - if (Array.isArray(pkg)) { - let _pkg = pkg[0]; - pkg[0] = require.resolve(_pkg); - return pkg; - } else { - return require.resolve(pkg); - } - }), - compact: false - } - } - ] - }; - - let dappBabelrc = path.join(realCwd, '.babelrc'); - if (fs.existsSync(dappBabelrc)) { - webpackOptions.module.rules[3].options.extends = dappBabelrc; + config = require(configPath); + // valid config types: https://webpack.js.org/configuration/configuration-types/ + // + function that returns a config object + // + function that returns a promise for a config object + // + array of named config objects + // + config object + if (typeof config === 'function') { + config = await config(this.webpackConfigName); + } else if (Array.isArray(config)) { + config = config.filter(cfg => cfg.name === this.webpackConfigName); + if (!config.length) { + return callback(`no webpack config has the name '${this.webpackConfigName}'`); } + if (config.length > 1) { + console.warn(`detected ${config.length} webpack configs having the name '${this.webpackConfigName}', using the first one`); + } + config = config[0]; } + } catch (e) { + console.error(`error while loading webpack config ${configPath}`); + callback(errorMessage(e)); + } - webpack(webpackOptions).run((err, stats) => { - async.waterfall([ - function checkStatsError(next) { - if (err) { - console.error(err); - return next(err); - } - if (!detectErrors) { - return next(); - } - if (stats.hasErrors()) { - return next( - stats.toJson(webpackOptions.stats).errors.join("\n") - ); - } - next(); - }//, - // function writeStatsReport(next) { - // if (detectErrors) { - // self._log('info', 'writing file '+ ('.embark/stats.report').bold.dim); - // } - // fs.writeFile( - // path.join(fs.dappPath('.embark'), 'stats.report'), - // stats.toString(webpackOptions.stats), - // next - // ); - // }, - // function writeStatsJSON(next) { - // if (detectErrors) { - // self._log('info','writing file '+ ('.embark/stats.json').bold.dim); - // } - // fs.writeFile( - // path.join(fs.dappPath('.embark'), 'stats.json'), - // JSON.stringify(stats.toJson(webpackOptions.stats)), - // next - // ); - // } - // note: to visualize the stats info in a browser, do... - // `npx webpack-bundle-analyzer /.embark/stats.json` - ], (err) => { - callback(err); - }); - }); + if (typeof config !== 'object' || config === null) { + return callback('bad webpack config, the resolved config was null or not an object'); + } + + webpack(config).run(async (err, stats) => { + if (err) { + return callback(errorMessage(err)); + } + if (!config.stats || config.stats === 'none') { + return callback(); + } + try { + this._log('info', 'writing file '+ ('.embark/stats.report').bold.dim); + await writeFile( + fs.dappPath('.embark/stats.report'), + stats.toString(config.stats) + ); + this._log('info', 'writing file ' + ('.embark/stats.json').bold.dim); + await writeFile( + fs.dappPath('.embark/stats.json'), + JSON.stringify(stats.toJson(config.stats)) + ); + if (stats.hasErrors()) { + const errors = stats.toJson(config.stats).errors.join('\n'); + return callback(errors); + } + callback(); + } catch (e) { + return callback(errorMessage(e)); + } }); } } @@ -214,7 +110,7 @@ process.on('message', (msg) => { } if (msg.action === constants.pipeline.build) { - return webpackProcess.build(msg.file, msg.importsList, (err) => { + return webpackProcess.build(msg.assets, msg.importsList, (err) => { process.send({result: constants.pipeline.built, error: err}); }); } diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 21b2ec2e5..808a45fea 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -200,10 +200,6 @@ function proposeAlternative(word, _dictionary, _exceptions) { return propose(word, dictionary, {threshold: 0.3}); } -function pwd() { - return process.env.PWD || process.cwd(); -} - function getExternalContractUrl(file) { const constants = require('../constants'); let url; @@ -462,6 +458,15 @@ function interceptLogs(consoleContext, logger) { }; } +function errorMessage(e) { + if (typeof e === 'string') { + return e; + } else if (e && e.message) { + return e.message; + } + return e; +} + module.exports = { joinPath, dirname, @@ -489,7 +494,6 @@ module.exports = { extractTar, extractZip, proposeAlternative, - pwd: pwd, getExternalContractUrl, toChecksumAddress, sha3, @@ -504,5 +508,6 @@ module.exports = { groupBy, sample, last, - interceptLogs + interceptLogs, + errorMessage }; diff --git a/package-lock.json b/package-lock.json index 81d36ff81..bc9660a65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1313,66 +1313,16 @@ "util.promisify": "^1.0.0" } }, - "babel-plugin-webpack-aliases": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-webpack-aliases/-/babel-plugin-webpack-aliases-1.1.3.tgz", - "integrity": "sha1-+7oor/c+SDc4949wRMGDrSF5toA=", + "babel-plugin-module-resolver": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.1.1.tgz", + "integrity": "sha512-1Q77Al4ydp6nYApJ7sQ2fmgz30WuQgJZegIYuyOdbdpxenB/bSezQ3hDPsumIXGlUS4vUIv+EwFjzzXZNWtARw==", "requires": { - "babel-types": "^6.5.2", - "find-up": "1.1.2" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" - } + "find-babel-config": "^1.1.0", + "glob": "^7.1.2", + "pkg-up": "^2.0.0", + "reselect": "^3.0.1", + "resolve": "^1.4.0" } }, "balanced-match": { @@ -2162,6 +2112,18 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, + "compression-webpack-plugin": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-1.1.11.tgz", + "integrity": "sha512-ZVWKrTQhtOP7rDx3M/koXTnRm/iwcYbuCdV+i4lZfAIe32Mov7vUVM0+8Vpz4q0xH+TBUZxq+rM8nhtkDH50YQ==", + "requires": { + "cacache": "^10.0.1", + "find-cache-dir": "^1.0.0", + "neo-async": "^2.5.0", + "serialize-javascript": "^1.4.0", + "webpack-sources": "^1.0.1" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3631,6 +3593,15 @@ } } }, + "find-babel-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.1.0.tgz", + "integrity": "sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=", + "requires": { + "json5": "^0.5.1", + "path-exists": "^3.0.0" + } + }, "find-cache-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", @@ -5595,6 +5566,11 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -7856,6 +7832,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", + "integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=" + }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", diff --git a/package.json b/package.json index b4d73f520..5f0d97f7d 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,13 @@ "ascii-table": "0.0.9", "async": "^2.0.1", "babel-loader": "8.0.0-beta.4", - "babel-plugin-webpack-aliases": "^1.1.3", + "babel-plugin-module-resolver": "^3.1.1", "bip39": "^2.5.0", "chokidar": "^2.0.3", "clone-deep": "^4.0.0", "colors": "^1.1.2", "commander": "^2.15.1", + "compression-webpack-plugin": "^1.1.11", "css-loader": "^0.28.11", "decompress": "^4.2.0", "deep-equal": "^1.0.1", @@ -57,6 +58,7 @@ "ipfs-api": "17.2.4", "is-valid-domain": "0.0.5", "live-plugin-manager-git-fix": "^0.12.1", + "lodash.clonedeep": "^4.5.0", "merge": "^1.2.0", "mocha": "^5.2.0", "multihashes": "^0.4.13", diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 0967ef424..000000000 --- a/webpack.config.js +++ /dev/null @@ -1 +0,0 @@ -{}