diff --git a/.travis.yml b/.travis.yml index 9b3be864de..bcb67c8857 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_script: script: - npm run lint - - npm run build + - npm run build:prod - npm test cache: diff --git a/appveyor.yml b/appveyor.yml index 0cfd6bd20a..e7fd287cbb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,7 @@ install: build_script: - npm run lint - - npm run build + - npm run build:prod test_script: - npm test diff --git a/dev_server.js b/dev_server.js index 4ee7cf5df9..32f1a37d61 100644 --- a/dev_server.js +++ b/dev_server.js @@ -1,10 +1,12 @@ -var express = require('express') -var webpack = require('webpack') -var config = require('./webpack.config') +'use strict' -var app = express() -var compiler = webpack(config) -var port = process.env.PORT || 3000 +const express = require('express') +const webpack = require('webpack') +const config = require('./webpack.development') + +const app = express() +const compiler = webpack(config) +const port = process.env.PORT || 3000 app.use(require('webpack-dev-middleware')(compiler, { publicPath: config.output.publicPath diff --git a/package.json b/package.json index 2044f25e3b..da66d7316c 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,16 @@ "test": "electron-mocha --renderer --require ts-node/register test/*.ts test/*.tsx", "postinstall": "typings install", "start-server": "node dev_server.js", - "start": "npm run build && npm-run-all --parallel dev start-server", - "dev": "env NODE_ENV=development node script/run", - "compile": "tsc && webpack", - "build": "npm run compile && node script/package", + "start": "npm run build:dev && npm-run-all --parallel run start-server", + "run": "env NODE_ENV=development node script/run", + "compile:dev": "tsc && env NODE_ENV=development webpack --config webpack.development.js", + "compile:prod": "tsc && env NODE_ENV=production webpack --config webpack.production.js", + "build:dev": "npm run compile:dev && env NODE_ENV=development node script/build", + "build:prod": "npm run compile:prod && env NODE_ENV=production node script/build", + "package": "node script/package", "clean": "rm -rf build", - "rebuild": "npm run clean && npm run build", + "rebuild:dev": "npm run clean && npm run build:dev", + "rebuild:prod": "npm run clean && npm run build:prod", "lint": "tslint ./src/**/*.ts ./src/**/*.tsx ./test/**/*.ts ./test/**/*.tsx" }, "author": "", @@ -28,7 +32,9 @@ "babel-runtime": "^6.6.1", "octokat": "^0.5.0-beta.0", "react": "^15.0.2", - "react-dom": "^15.0.2", + "react-dom": "^15.0.2" + }, + "debugDependencies": { "react-transform-hmr": "^1.0.4", "webpack-hot-middleware": "^2.10.0" }, @@ -45,6 +51,7 @@ "electron-mocha": "2.1.0", "electron-packager": "^7.0.1", "electron-prebuilt": "^1.1.2", + "electron-winstaller": "^2.3.0", "express": "^4.13.4", "fs-extra": "^0.30.0", "mocha": "^2.4.5", diff --git a/script/build b/script/build new file mode 100755 index 0000000000..b2475728ce --- /dev/null +++ b/script/build @@ -0,0 +1,93 @@ +#!/usr/bin/env node + +'use strict' + +const path = require('path') +const cp = require('child_process') +const fs = require('fs-extra') +const packager = require('electron-packager') + +const projectRoot = path.join(__dirname, '..') +const buildRoot = path.join(projectRoot, 'build') +const appPackage = require(path.join(projectRoot, 'package.json')) + +console.log(`Building for ${process.env.NODE_ENV}…`) + +console.log('Removing old distribution…') +fs.removeSync(path.join(projectRoot, 'dist')) + +console.log('Installing dependencies…') +installDependencies() + +console.log('Copying `static`…') +fs.copySync(path.join(projectRoot, 'static'), path.join(buildRoot, 'static'), {clobber: true}) + +const options = { + platform: process.platform, + arch: 'x64', + 'app-version': appPackage.version, + 'build-version': appPackage.version, + asar: false, // TODO: Probably wanna enable this down the road. + out: path.join(projectRoot, 'dist'), + icon: path.join(buildRoot, 'static', 'icon'), + dir: path.join(projectRoot, 'build'), + overwrite: true, + prune: false, // We only install production dependencies above. + + // OS X + // TODO: We'll need to sign this shit. + 'app-bundle-id': appPackage.bundleID, + 'app-category-type': 'public.app-category.developer-tools', + + // Windows + 'version-string': { + 'CompanyName': appPackage.companyName, + 'FileDescription': '', + 'OriginalFilename': '', + 'ProductName': appPackage.productName, + 'InternalName': appPackage.productName + } +} + +packager(options, (error, appPaths) => { + if (error) { + console.error(error) + process.exit(1) + } else { + console.log(`Built to ${appPaths}`) + process.exit(0) + } +}) + +function installDependencies () { + let packageJson = appPackage + if (process.env.NODE_ENV === 'development') { + packageJson = debugPackageJson(packageJson) + } else { + packageJson = releasePackageJson(packageJson) + } + + fs.removeSync(path.join(buildRoot, 'package.json')) + fs.writeJsonSync(path.join(buildRoot, 'package.json'), packageJson) + cp.execSync('npm install', {cwd: buildRoot, env: process.env}) +} + +function distPackageJson (appPackage) { + const releasePackage = Object.assign({}, appPackage) + delete releasePackage['scripts'] + delete releasePackage['devDependencies'] + return releasePackage +} + +function releasePackageJson (appPackage) { + const releasePackage = Object.assign({}, distPackageJson(appPackage)) + delete releasePackage['debugDependencies'] + return releasePackage +} + +function debugPackageJson (appPackage) { + const releasePackage = Object.assign({}, distPackageJson(appPackage)) + releasePackage.dependencies = Object.assign({}, releasePackage.dependencies, releasePackage.debugDependencies) + delete releasePackage['debugDependencies'] + return releasePackage +} diff --git a/script/dist-info.js b/script/dist-info.js new file mode 100644 index 0000000000..a47ee24a79 --- /dev/null +++ b/script/dist-info.js @@ -0,0 +1,18 @@ +'use strict' + +const path = require('path') + +const projectRoot = path.join(__dirname, '..') +const appPackage = require(path.join(projectRoot, 'package.json')) + +module.exports.getDistPath = function () { + return path.join(projectRoot, 'dist', `${appPackage.productName}-${process.platform}-x64`) +} + +module.exports.getProductName = function () { + return appPackage.productName +} + +module.exports.getCompanyName = function () { + return appPackage.companyName +} diff --git a/script/package b/script/package old mode 100755 new mode 100644 index a8d1c16b1f..dc65877fa7 --- a/script/package +++ b/script/package @@ -2,58 +2,45 @@ 'use strict' -const path = require('path') -const cp = require('child_process') const fs = require('fs-extra') -const packager = require('electron-packager') +const cp = require('child_process') +const path = require('path') +const distInfo = require('./dist-info') -const projectRoot = path.join(__dirname, '..') -const buildRoot = path.join(projectRoot, 'build') -const appPackage = require(path.join(projectRoot, 'package.json')) +const distPath = distInfo.getDistPath() +const productName = distInfo.getProductName() -console.log('Removing old distribution…') -fs.removeSync(path.join(projectRoot, 'dist')) - -console.log('Installing dependencies…') -fs.copySync(path.join(projectRoot, 'package.json'), path.join(buildRoot, 'package.json'), {clobber: true}) -cp.execSync('npm install --only=production', {cwd: buildRoot}) - -console.log('Copying `static`…') -fs.copySync(path.join(projectRoot, 'static'), path.join(buildRoot, 'static'), {clobber: true}) - -const options = { - platform: process.platform, - arch: 'x64', - 'app-version': appPackage.version, - 'build-version': appPackage.version, - asar: false, // TODO: Probably wanna enable this down the road. - out: path.join(projectRoot, 'dist'), - icon: path.join(buildRoot, 'static', 'icon'), - dir: path.join(projectRoot, 'build'), - overwrite: true, - prune: false, // We only install production dependencies above. - - // OS X - 'app-bundle-id': appPackage.bundleID, - 'app-category-type': 'public.app-category.developer-tools', - - // Windows - 'version-string': { - 'CompanyName': appPackage.companyName, - 'FileDescription': '', - 'OriginalFilename': '', - 'ProductName': appPackage.productName, - 'InternalName': appPackage.productName - } +if (process.platform === 'darwin') { + packageOSX() +} else if (process.platform === 'win32') { + packageWindows() +} else { + console.error(`I dunno how to package for ${process.platform} :(`) + process.exit(1) } -console.log('Packaging…') -packager(options, (error, appPaths) => { - if (error) { - console.error(error) - process.exit(1) - } else { - console.log(`Built to ${appPaths}`) - process.exit(0) - } -}) +function packageOSX () { + const dest = path.join(distPath, '..', `${productName}.zip`) + fs.removeSync(dest) + + cp.execSync(`ditto -ck --keepParent ${distPath}/${productName}.app ${dest}`) + console.log(`Zipped to ${dest}`) +} + +function packageWindows () { + const electronInstaller = require('electron-winstaller') + const outputDir = path.join(distPath, '..', 'installer') + // TODO: We'll need to sign this shit. + electronInstaller + .createWindowsInstaller({ + appDirectory: distPath, + outputDirectory: outputDir, + authors: distInfo.getCompanyName(), + exe: `${productName}.exe` + }) + .then(() => console.log(`Installers created in ${outputDir}`)) + .catch(e => { + console.error(`Error packaging: ${e}`) + process.exit(1) + }) +} diff --git a/script/run b/script/run index aa478cf3ca..52bb939cb3 100644 --- a/script/run +++ b/script/run @@ -4,10 +4,9 @@ const path = require('path') const cp = require('child_process') +const distInfo = require('./dist-info') -const projectRoot = path.join(__dirname, '..') -const appPackage = require(path.join(projectRoot, 'package.json')) -const distPath = path.join(projectRoot, 'dist', `${appPackage.productName}-${process.platform}-x64`) +const distPath = distInfo.getDistPath() let binaryPath = '' if (process.platform === 'darwin') { diff --git a/webpack.common.js b/webpack.common.js new file mode 100644 index 0000000000..903130406e --- /dev/null +++ b/webpack.common.js @@ -0,0 +1,38 @@ +'use strict' + +const path = require('path') + +module.exports = { + entry: [ + './src/index' + ], + output: { + filename: 'bundle.js', + path: path.join(__dirname, 'build'), + libraryTarget: 'commonjs2' + }, + module: { + loaders: [ + { + test: /\.tsx?$/, + loaders: ['babel', 'ts'], + include: path.join(__dirname, 'src') + } + ] + }, + resolve: { + extensions: ['', '.js', '.ts', '.tsx'], + packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] + }, + target: 'electron', + externals: function (context, request, callback) { + try { + // Attempt to resolve the module via Node + require.resolve(request) + callback(null, request) + } catch (e) { + // Node couldn't find it, so it must be user-aliased + callback() + } + } +} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index b004daf57c..0000000000 --- a/webpack.config.js +++ /dev/null @@ -1,56 +0,0 @@ -var path = require('path') -var webpack = require('webpack') -var webpackTargetElectronRenderer = require('webpack-target-electron-renderer') - -var config = { - devtool: 'cheap-module-eval-source-map', - entry: [ - 'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr', - './src/index' - ], - output: { - filename: 'bundle.js', - path: path.join(__dirname, 'build'), - libraryTarget: 'commonjs2', - publicPath: 'http://localhost:3000/build/' - }, - plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin(), - new webpack.DefinePlugin({ - // TODO: This is obviously wrong for production builds. - __DEV__: true, - 'process.env': { - NODE_ENV: JSON.stringify('development') - } - }) - ], - module: { - loaders: [ - { - test: /\.tsx?$/, - loaders: ['babel', 'ts'], - include: path.join(__dirname, 'src') - } - ] - }, - resolve: { - extensions: ['', '.js', '.ts', '.tsx'], - packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'] - }, - target: 'electron', - externals: function (context, request, callback) { - try { - // Attempt to resolve the module via Node - require.resolve(request) - callback(null, request) - } catch (e) { - // Node couldn't find it, so it must be user-aliased - callback() - } - } -} - -config.target = webpackTargetElectronRenderer(config) - -module.exports = config diff --git a/webpack.development.js b/webpack.development.js new file mode 100644 index 0000000000..8e306cf1e8 --- /dev/null +++ b/webpack.development.js @@ -0,0 +1,35 @@ +'use strict' + +const common = require('./webpack.common') + +const webpack = require('webpack') +const webpackTargetElectronRenderer = require('webpack-target-electron-renderer') + +const config = { + devtool: 'cheap-module-eval-source-map', + entry: [ + 'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr', + ...common.entry + ], + output: { + filename: common.output.filename, + path: common.output.path, + libraryTarget: common.output.libraryTarget, + publicPath: 'http://localhost:3000/build/' + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin(), + new webpack.DefinePlugin({ + __DEV__: true + }) + ], + module: common.module, + resolve: common.resolve, + target: 'electron', + externals: common.externals +} + +config.target = webpackTargetElectronRenderer(config) + +module.exports = config diff --git a/webpack.production.js b/webpack.production.js new file mode 100644 index 0000000000..ca62bbc31e --- /dev/null +++ b/webpack.production.js @@ -0,0 +1,27 @@ +'use strict' + +const common = require('./webpack.common') + +const webpack = require('webpack') +const webpackTargetElectronRenderer = require('webpack-target-electron-renderer') + +const config = { + devtool: 'cheap-module-source-map', + entry: common.entry, + output: common.output, + plugins: [ + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.OccurrenceOrderPlugin(true), + new webpack.DefinePlugin({ + __DEV__: false + }) + ], + module: common.module, + resolve: common.resolve, + target: common.target, + externals: common.externals +} + +config.target = webpackTargetElectronRenderer(config) + +module.exports = config