Skip to content

Commit

Permalink
feat: support dll plugin, Close #2
Browse files Browse the repository at this point in the history
  • Loading branch information
sorrycc committed Mar 29, 2017
1 parent af60ee2 commit b44d62c
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions bin/roadhog.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ switch (script) {
console.log(require('../package.json').version);
break;
case 'build':
case 'buildDll':
case 'server':
case 'test':
require('atool-monitor').emit();
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
"json-loader": "^0.5.4",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"lodash.pullall": "^4.2.0",
"lodash.uniq": "^4.5.0",
"mocha": "^3.2.0",
"parse-json-pretty": "^0.1.0",
"postcss": "^5.2.6",
Expand Down
176 changes: 176 additions & 0 deletions src/buildDll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import filesize from 'filesize';
import { sync as gzipSize } from 'gzip-size';
import webpack from 'webpack';
import recursive from 'recursive-readdir';
import stripAnsi from 'strip-ansi';
import getPaths from './config/paths';
import getConfig from './utils/getConfig';
import applyWebpackConfig, { warnIfExists } from './utils/applyWebpackConfig';

process.env.NODE_ENV = process.env.NODE_ENV || 'production';

const argv = require('yargs')
.usage('Usage: roadhog buildDll [options]')
.help('h')
.argv;

let rcConfig;
let outputPath;
let appBuild;
let config;

export function build(argv) {
const paths = getPaths(argv.cwd);

try {
rcConfig = getConfig(process.env.NODE_ENV, argv.cwd);
} catch (e) {
console.log(chalk.red('Failed to parse .roadhogrc config.'));
console.log();
console.log(e.message);
process.exit(1);
}

if (!rcConfig.dllPlugin) {
console.log(chalk.red('dllPlugin config not found in .roadhogrc'));
process.exit(1);
}

appBuild = paths.dllNodeModule;
config = applyWebpackConfig(
require('./config/webpack.config.dll')(argv, rcConfig, paths),
process.env.NODE_ENV,
);

return new Promise((resolve) => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
recursive(appBuild, (err, fileNames) => {
const previousSizeMap = (fileNames || [])
.filter(fileName => /\.(js|css)$/.test(fileName))
.reduce((memo, fileName) => {
const contents = fs.readFileSync(fileName);
const key = removeFileNameHash(fileName);
memo[key] = gzipSize(contents);
return memo;
}, {});

// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(appBuild);

// Start the webpack build
realBuild(previousSizeMap, resolve, argv);
});
});
}

// Input: /User/dan/app/build/static/js/main.82be8.js
// Output: /static/js/main.js
function removeFileNameHash(fileName) {
return fileName
.replace(appBuild, '')
.replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3);
}

// Input: 1024, 2048
// Output: "(+1 KB)"
function getDifferenceLabel(currentSize, previousSize) {
const FIFTY_KILOBYTES = 1024 * 50;
const difference = currentSize - previousSize;
const fileSize = !Number.isNaN(difference) ? filesize(difference) : 0;
if (difference >= FIFTY_KILOBYTES) {
return chalk.red(`+${fileSize}`);
} else if (difference < FIFTY_KILOBYTES && difference > 0) {
return chalk.yellow(`+${fileSize}`);
} else if (difference < 0) {
return chalk.green(fileSize);
} else {
return '';
}
}

// Print a detailed summary of build files.
function printFileSizes(stats, previousSizeMap) {
const assets = stats.toJson().assets
.filter(asset => /\.(js|css)$/.test(asset.name))
.map((asset) => {
const fileContents = fs.readFileSync(`${appBuild}/${asset.name}`);
const size = gzipSize(fileContents);
const previousSize = previousSizeMap[removeFileNameHash(asset.name)];
const difference = getDifferenceLabel(size, previousSize);
return {
folder: path.join(appBuild, path.dirname(asset.name)),
name: path.basename(asset.name),
size,
sizeLabel: filesize(size) + (difference ? ` (${difference})` : ''),
};
});
assets.sort((a, b) => b.size - a.size);
const longestSizeLabelLength = Math.max.apply(
null,
assets.map(a => stripAnsi(a.sizeLabel).length),
);
assets.forEach((asset) => {
let sizeLabel = asset.sizeLabel;
const sizeLength = stripAnsi(sizeLabel).length;
if (sizeLength < longestSizeLabelLength) {
const rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength);
sizeLabel += rightPadding;
}
console.log(
` ${sizeLabel} ${chalk.dim(asset.folder + path.sep)}${chalk.cyan(asset.name)}`,
);
});
}

// Print out errors
function printErrors(summary, errors) {
console.log(chalk.red(summary));
console.log();
errors.forEach((err) => {
console.log(err.message || err);
console.log();
});
}

function doneHandler(previousSizeMap, argv, resolve, err, stats) {
if (err) {
printErrors('Failed to compile.', [err]);
process.exit(1);
}

if (stats.compilation.errors.length) {
printErrors('Failed to compile.', stats.compilation.errors);
process.exit(1);
}

warnIfExists();

console.log(chalk.green('Compiled successfully.'));
console.log();

console.log('File sizes after gzip:');
console.log();
printFileSizes(stats, previousSizeMap);
console.log();

resolve();
}

// Create the production build and print the deployment instructions.
function realBuild(previousSizeMap, resolve, argv) {
console.log('Creating dll bundle...');

const compiler = webpack(config);
const done = doneHandler.bind(null, previousSizeMap, argv, resolve);
compiler.run(done);
}

// Run.
if (require.main === module) {
build({ ...argv, cwd: process.cwd() });
}
2 changes: 2 additions & 0 deletions src/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export default function getPaths(cwd) {
appSrc: resolveApp('src'),
appNodeModules: resolveApp('node_modules'),
ownNodeModules: resolveOwn('../../node_modules'),
dllNodeModule: resolveApp('node_modules/roadhog-dlls'),
dllManifest: resolveApp('node_modules/roadhog-dlls/roadhog.json'),
resolveApp,
appDirectory,
};
Expand Down
16 changes: 16 additions & 0 deletions src/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import webpack from 'webpack';
import fs from 'fs';
import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
import SystemBellWebpackPlugin from 'system-bell-webpack-plugin';
import { join } from 'path';
import getPaths from './paths';
import getEntry from '../utils/getEntry';
import getTheme from '../utils/getTheme';
Expand All @@ -30,6 +31,19 @@ export default function (config, cwd) {
const theme = JSON.stringify(getTheme(process.cwd(), config));
const paths = getPaths(cwd);

const dllPlugins = config.dllPlugin ? [
new webpack.DllReferencePlugin({
context: paths.appSrc,
manifest: require(paths.dllManifest), // eslint-disable-line
}),
new CopyWebpackPlugin([
{
from: join(paths.dllNodeModule, 'roadhog.dll.js'),
to: join(paths.appBuild, 'roadhog.dll.js'),
},
]),
] : [];

const finalWebpackConfig = {
devtool: 'cheap-module-source-map',
entry: getEntry(config, paths.appDirectory),
Expand Down Expand Up @@ -147,6 +161,8 @@ export default function (config, cwd) {
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new SystemBellWebpackPlugin(),
].concat(
dllPlugins
).concat(
!fs.existsSync(paths.appPublic) ? [] :
new CopyWebpackPlugin([
{
Expand Down
37 changes: 37 additions & 0 deletions src/config/webpack.config.dll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import webpack from 'webpack';
import { join } from 'path';

import pullAll from 'lodash.pullall';
import uniq from 'lodash.uniq';

export default function (argv, rcConfig, paths) {
const appBuild = paths.dllNodeModule;
const pkg = require(join(paths.appDirectory, 'package.json')); // disable-eslint-line

const { include, exclude } = rcConfig.dllPlugin || {};

const dependencyNames = Object.keys(pkg.dependencies);
const includeDependencies = uniq(dependencyNames.concat(include || []));

return {
entry: {
roadhog: pullAll(includeDependencies, exclude),
},
output: {
path: appBuild,
filename: '[name].dll.js',
library: '[name]',
},
plugins: [
new webpack.DllPlugin({
path: join(appBuild, '[name].json'),
name: '[name]',
context: paths.appSrc,
}),
],
resolve: {
root: paths.appDirectory,
modulesDirectories: ['node_modules'],
},
};
}
7 changes: 7 additions & 0 deletions src/runServer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import detect from 'detect-port';
import fs from 'fs';
import clearConsole from 'react-dev-utils/clearConsole';
import getProcessForPort from 'react-dev-utils/getProcessForPort';
import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages';
Expand Down Expand Up @@ -211,6 +212,12 @@ function run(port) {

function init() {
readRcConfig();

if (rcConfig.dllPlugin && !fs.existsSync(paths.dllManifest)) {
console.log(chalk.red('Failed to start the server, since you have enabled dllPlugin, but have not run `roadhog buildDll` before `roadhog server`.'));
process.exit(1);
}

readWebpackConfig();
detect(DEFAULT_PORT).then((port) => {
if (port === DEFAULT_PORT) {
Expand Down

0 comments on commit b44d62c

Please sign in to comment.