Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@
"uuid": "^3.0.1"
},
"dependencies": {
"@webpack-blocks/css-modules": "^0.4.0",
"@webpack-blocks/dev-server2": "^0.4.0",
"@webpack-blocks/postcss": "^0.4.3",
"@webpack-blocks/webpack2": "^0.4.0",
"autoprefixer": "^7.1.0",
"babel-loader": "^7.0.0",
"babel-plugin-jsx-pragmatic": "^1.0.2",
Expand All @@ -111,12 +107,14 @@
"babel-register": "^6.24.1",
"bluebird": "^3.5.0",
"chalk": "^2.1.0",
"console-clear": "^1.0.0",
"copy-webpack-plugin": "^4.0.1",
"cross-spawn-promise": "^0.10.1",
"css-loader": "^0.28.7",
"css-modules-require-hook": "^4.0.6",
"devcert-san": "^0.3.3",
"ejs-loader": "^0.3.0",
"extract-text-webpack-plugin": "^2.1.2",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.1",
"fs.promised": "^3.0.0",
"get-port": "^3.1.0",
Expand All @@ -138,6 +136,7 @@
"ora": "^1.2.0",
"persist-path": "^1.0.1",
"postcss-less": "^0.16.1",
"postcss-loader": "^2.0.6",
"preact": "^8.1.0",
"preact-compat": "^3.14.3",
"preact-render-to-string": "^3.6.0",
Expand All @@ -151,14 +150,16 @@
"simplehttp2server": "^2.0.0",
"source-map": "^0.5.6",
"stack-trace": "0.0.10",
"style-loader": "^0.18.2",
"sw-precache-webpack-plugin": "^0.11.2",
"tmp": "0.0.31",
"unfetch": "^3.0.0",
"update-notifier": "^2.2.0",
"url-loader": "^0.5.8",
"webpack": "^2.3.3",
"webpack": "^3.6.0",
"webpack-chunk-hash": "^0.4.0",
"webpack-dev-server": "^2.6.1",
"webpack-dev-server": "^2.8.2",
"webpack-merge": "^4.1.0",
"webpack-plugin-replace": "^1.1.1",
"which": "^1.2.14",
"yargs": "^8.0.1"
Expand Down
1 change: 1 addition & 0 deletions src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default asyncCommand({
}

let stats = await runWebpack(false, argv);

showStats(stats);

if (argv.json) {
Expand Down
7 changes: 2 additions & 5 deletions src/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,8 @@ const SERVERS = {


/** Create a temporary file. See https://npm.im/tmp */
const tmpFile = opts => new Promise( (resolve, reject) => {
tmp.file(opts, (err, path) => {
if (err) reject(err);
else resolve(path);
});
const tmpFile = opts => new Promise((res, rej) => {
tmp.file(opts, (err, path) => err ? rej(err) : res(path));
});


Expand Down
16 changes: 8 additions & 8 deletions src/lib/webpack/prerender.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import chalk from 'chalk';
import { resolve } from 'path';
import fs from 'fs';
import { readFileSync } from 'fs';
import stackTrace from 'stack-trace';
import { SourceMapConsumer } from 'source-map';
import chalk from 'chalk';

export default function prerender(env, params) {
params = params || {};

let entry = resolve(env.dest, './ssr-build/ssr-bundle.js'),
url = params.url || '/';
let entry = resolve(env.dest, './ssr-build/ssr-bundle.js');
let url = params.url || '/';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is let neccesary here? Can this be a const?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@developit prefers let for most things. I broke the rule & used const a few times to be emphatic.


global.location = { href:url, pathname:url };
global.history = {};
global.location = { href:url, pathname:url };

try {
let m = require(entry),
Expand Down Expand Up @@ -44,7 +44,7 @@ const handlePrerenderError = (err, env, stack, entry) => {
let sourceMapContent, position, sourcePath, sourceLines, sourceCodeHighlight;

try {
sourceMapContent = JSON.parse(fs.readFileSync(`${entry}.map`));
sourceMapContent = JSON.parse(readFileSync(`${entry}.map`));
} catch (err) {
process.stderr.write(chalk.red(`Unable to read sourcemap: ${entry}.map\n`));
}
Expand All @@ -61,10 +61,10 @@ const handlePrerenderError = (err, env, stack, entry) => {
sourcePath = resolve(env.src, position.source);
sourceLines;
try {
sourceLines = fs.readFileSync(sourcePath, 'utf-8').split('\n');
sourceLines = readFileSync(sourcePath, 'utf-8').split('\n');
} catch (err) {
try {
sourceLines = fs.readFileSync(require.resolve(position.source), 'utf-8').split('\n');
sourceLines = readFileSync(require.resolve(position.source), 'utf-8').split('\n');
} catch (err) {
process.stderr.write(chalk.red(`Unable to read file: ${sourcePath}\n`));
}
Expand Down
44 changes: 44 additions & 0 deletions src/lib/webpack/render-html-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { resolve } from 'path';
import { existsSync } from 'fs';
import HtmlWebpackExcludeAssetsPlugin from 'html-webpack-exclude-assets-plugin';
import ScriptExtHtmlWebpackPlugin from 'script-ext-html-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { readJson } from './webpack-base-config';
import prerender from './prerender';

export default function (config) {
const { cwd, dest, isProd, src } = config;

const htmlWebpackConfig = ({ url, title }) => ({
filename: resolve(dest, url.substring(1), 'index.html'),
template: `!!ejs-loader!${config.template || resolve(__dirname, '../../resources/template.html')}`,
minify: isProd && {
collapseWhitespace: true,
removeScriptTypeAttributes: true,
removeRedundantAttributes: true,
removeStyleLinkTypeAttributes: true,
removeComments: true
},
favicon: existsSync(resolve(src, 'assets/favicon.ico')) ? 'assets/favicon.ico' : resolve(__dirname, '../../resources/favicon.ico'),
manifest: config.manifest,
inject: true,
compile: true,
preload: config.preload===true,
title: title || config.title || config.manifest.name || config.manifest.short_name || (config.pkg.name || '').replace(/^@[a-z]\//, '') || 'Preact App',
excludeAssets: [/(bundle|polyfills)(\..*)?\.js$/],
config,
ssr(params) {
return config.prerender ? prerender({ cwd, dest, src }, { ...params, url }) : '';
}
});

const pages = readJson(resolve(cwd, config.prerenderUrls || '')) || [{ url: '/' }];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should't this be using params.url for the fallback?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pages? The params is only within the ssr function. I think the / route is a safe default. Plus, it's just copied over from what was there already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea maybe we should do it in a separate PR. Basically, the whole "subdirectory" stuff is something we should fix. But yea, let's keep it separate.


return pages.map(htmlWebpackConfig).map(conf => new HtmlWebpackPlugin(conf)).concat([
new HtmlWebpackExcludeAssetsPlugin(),
new ScriptExtHtmlWebpackPlugin({
// inline: 'bundle.js',
defaultAttribute: 'defer'
})
]);
}
112 changes: 54 additions & 58 deletions src/lib/webpack/run-webpack.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,48 @@
import path from 'path';
import fs from 'fs.promised';
import ip from 'ip';
import { resolve } from 'path';
import { writeFile } from 'fs.promised';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import chalk from 'chalk';
import getPort from 'get-port';
import clearConsole from 'console-clear';
import DevServer from 'webpack-dev-server';
import clientConfig from './webpack-client-config';
import serverConfig from './webpack-server-config';
import transformConfig from './transform-config';
import { error, isDir, warn } from '../../util';

export default async (watch=false, env, onprogress) => {
if (watch) {
return await devBuild(env, onprogress);
}
export default function (watch=false, env, onprogress) {
env.isProd = env.production; // shorthand
env.cwd = resolve(env.cwd || process.cwd());

// env.src='src' via `build` default
let src = resolve(env.cwd, env.src);
env.src = isDir(src) ? src : env.cwd;

// attach sourcing helper
env.source = dir => resolve(env.src, dir);

return await prodBuild(env);
};
// determine build-type to run
let fn = watch ? devBuild : prodBuild;
return fn(env, onprogress); // AsyncFunctioon
}

const devBuild = async (env, onprogress) => {
async function devBuild(env, onprogress) {
let config = clientConfig(env);

await transformConfig(env, config);

let userPort = parseInt(process.env.PORT || config.devServer.port, 10) || 8080;
let port = await getPort(userPort);

let compiler = webpack(config);
return await new Promise((resolve, reject) => {

return await new Promise((_, rej) => {
compiler.plugin('emit', (compilation, callback) => {
var missingDeps = compilation.missingDependencies;
var nodeModulesPath = path.resolve(__dirname, '../../../node_modules');
var nodeModulesPath = resolve(__dirname, '../../../node_modules');

// ...tell webpack to watch node_modules recursively until they appear.
if (missingDeps.some(file => file.indexOf(nodeModulesPath) !== -1)) {
// ...tell webpack to watch node_modules recursively until they appear.
compilation.contextDependencies.push(nodeModulesPath);
}

Expand All @@ -42,14 +52,15 @@ const devBuild = async (env, onprogress) => {
compiler.plugin('done', stats => {
let devServer = config.devServer;
let protocol = (process.env.HTTPS || devServer.https) ? 'https' : 'http';
let host = process.env.HOST || devServer.host || 'localhost';

let host = process.env.HOST || devServer.host || 'localhost';
if (host === '0.0.0.0') host = 'localhost';

let serverAddr = `${protocol}://${host}:${chalk.bold(port)}`;
let localIpAddr = `${protocol}://${ip.address()}:${chalk.bold(port)}`;

clearConsole();

if (stats.hasErrors()) {
process.stdout.write(chalk.red('\Build failed!\n\n'));
} else {
Expand All @@ -65,18 +76,18 @@ const devBuild = async (env, onprogress) => {

if (onprogress) onprogress(stats);
});
compiler.plugin('failed', reject);

let server = new WebpackDevServer(compiler, config.devServer);
server.listen(port);
compiler.plugin('failed', rej);

new DevServer(compiler, config.devServer).listen(port);
});
};
}

const prodBuild = async (env) => {
async function prodBuild(env) {
let config = clientConfig(env);

await transformConfig(env, config);
let serverCompiler, clientCompiler = webpack(config);
let serverCompiler, clientCompiler=webpack(config);

if (env.prerender) {
let ssrConfig = serverConfig(env);
Expand All @@ -88,71 +99,58 @@ const prodBuild = async (env) => {
let stats = await runCompiler(clientCompiler);

// Timeout for plugins that work on `after-emit` event of webpack
await new Promise(r => setTimeout(()=> r(), 20));
await new Promise(r => setTimeout(r, 20));

return stats;
};
}

const runCompiler = compiler => new Promise((resolve, reject) => {
const runCompiler = compiler => new Promise((res, rej) => {
compiler.run((err, stats) => {
if (err || stats.hasErrors()) {
showStats(stats);
reject(chalk.red('Build failed!'));
rej(chalk.red('Build failed!'));
}

resolve(stats);
res(stats);
});
});

export function showStats(stats) {
let info = stats.toJson("errors-only");
let info = stats.toJson('errors-only');

if (stats.hasErrors()) {
info.errors.map(stripBabelLoaderPrefix).forEach( message => {
process.stderr.write(chalk.red(message)+'\n');
});
info.errors.map(stripBabelLoaderPrefix).forEach(msg => error(msg));
}

if (stats.hasWarnings()) {
info.warnings.map(stripBabelLoaderPrefix).forEach( message => {
process.stderr.write(chalk.yellow(message)+'\n');
});
info.warnings.map(stripBabelLoaderPrefix).forEach(msg => warn(msg));
}

return stats;
}

export function writeJsonStats(stats) {
let outputPath = path.resolve(process.cwd(), 'stats.json');
let jsonStats = stats.toJson({
json: true,
chunkModules: true,
source: false,
});
let outputPath = resolve(process.cwd(), 'stats.json');
let jsonStats = stats.toJson({ json:true, chunkModules:true, source:false });

jsonStats = (jsonStats.children && jsonStats.children[0]) || jsonStats;

jsonStats.modules.forEach(stripBabelLoaderFromModuleNames);
jsonStats.chunks.forEach(c => c.modules.forEach(stripBabelLoaderFromModuleNames));

return fs.writeFile(outputPath, JSON.stringify(jsonStats))
.then(() => {
process.stdout.write('\nWebpack output stats generated.\n\n');
process.stdout.write('You can upload your stats.json to:\n');
process.stdout.write('- https://chrisbateman.github.io/webpack-visualizer/\n');
process.stdout.write('- https://webpack.github.io/analyse/\n');
});
jsonStats.chunks.forEach(c => c.forEachModule(stripBabelLoaderFromModuleNames));

return writeFile(outputPath, JSON.stringify(jsonStats)).then(() => {
process.stdout.write('\nWebpack output stats generated.\n\n');
process.stdout.write('You can upload your stats.json to:\n');
process.stdout.write('- https://chrisbateman.github.io/webpack-visualizer/\n');
process.stdout.write('- https://webpack.github.io/analyse/\n');
});
}

const clearConsole = () => {
process.stdout.write(
process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'
);
};
const keysToNormalize = ['identifier', 'name', 'module', 'moduleName', 'moduleIdentifier'];

const stripBabelLoaderFromModuleNames = m => {
const keysToNormalize = ['identifier', 'name', 'module', 'moduleName', 'moduleIdentifier'];
const stripBabelLoaderPrefix = log => log.replace(/@?\s*(\.\/~\/babel-loader\/lib\?{[\s\S]*?}!)/g, '');

function stripBabelLoaderFromModuleNames(m) {
keysToNormalize.forEach(key => {
if (key in m) {
m[key] = stripBabelLoaderPrefix(m[key]);
Expand All @@ -164,6 +162,4 @@ const stripBabelLoaderFromModuleNames = m => {
}

return m;
};

const stripBabelLoaderPrefix = log => log.replace(/@?\s*(\.\/~\/babel-loader\/lib\?{[\s\S]*?}!)/g, '');
}
4 changes: 1 addition & 3 deletions src/lib/webpack/transform-config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import path from 'path';
import fs from 'fs.promised';
import {
webpack,
} from '@webpack-blocks/webpack2';
import webpack from 'webpack';

export default async function (env, config, ssr = false) {
let transformerPath = path.resolve(env.cwd, env.config || './preact.config.js');
Expand Down
Loading