Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Prerender using webpack #71

Merged
merged 53 commits into from
Jun 28, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
eb9f9c1
Merge remote-tracking branch 'developit/master'
rkostrzewski May 29, 2017
5615460
feat(WebpackSSR): Prerender using webpack bundled code
rkostrzewski May 29, 2017
bd01055
Merge branch 'master' into feature/webpack-ssr
developit May 29, 2017
63e745a
feat(WebpackSSR): Single webpack compiler pass
rkostrzewski May 30, 2017
b18db39
refacotr(WebpackSSR): Move webpack stuff to webpack folder & create c…
rkostrzewski May 30, 2017
22e93c5
feat(WebpackSSR): Async component loading for server & build output i…
rkostrzewski May 31, 2017
b47314d
fix: JSON stats for new webpack build with two configs
rkostrzewski May 31, 2017
318c90f
chore: update sw-precache-webpack-plugin to 0.11.2
rkostrzewski Jun 1, 2017
881e476
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 1, 2017
864e55c
test: Preact CLI tests [WIP]
rkostrzewski Jun 7, 2017
8d5d286
test: Preact CLI tests [WIP]
rkostrzewski Jun 8, 2017
a70e062
test: Preact CLI tests
rkostrzewski Jun 10, 2017
f0b2020
Remove yarn.lock
rkostrzewski Jun 10, 2017
5287818
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 10, 2017
69829bb
refactor: Preact CLI tests
rkostrzewski Jun 11, 2017
7800035
chore: Setup Travis CI
rkostrzewski Jun 11, 2017
2197b93
fix: Setup test timeouts for travis
rkostrzewski Jun 11, 2017
32d6cfb
chore: Travis setup chrome path [WIP]
rkostrzewski Jun 11, 2017
8ae3a8d
fix: Setup test timeouts for travis
rkostrzewski Jun 11, 2017
266443b
chore: Debug Chrome errors on Travis
rkostrzewski Jun 11, 2017
332f858
Revert "chore: Debug Chrome errors on Travis"
rkostrzewski Jun 11, 2017
24840c6
chore: Travis setup chrome
rkostrzewski Jun 11, 2017
0706424
chore: Travis setup chrome
rkostrzewski Jun 11, 2017
b6d179a
chore: Travis setup chrome
rkostrzewski Jun 12, 2017
0eed33a
chore: Travis setup chrome
rkostrzewski Jun 12, 2017
ddd4700
chore: Travis setup chrome
rkostrzewski Jun 12, 2017
6a4b7e7
chore: Travis setup chrome
rkostrzewski Jun 12, 2017
2546b75
chore: Travis setup chrome
rkostrzewski Jun 12, 2017
f64422b
refactor: Preact CLI tests
rkostrzewski Jun 12, 2017
9b3bea3
test: Preact CLI increase build timeout
rkostrzewski Jun 12, 2017
17e8aa3
test: Increase robustness of serve test
rkostrzewski Jun 12, 2017
9395884
test: Don't fail when page can't be loaded
rkostrzewski Jun 13, 2017
5c10410
Revert "test: Don't fail when page can't be loaded"
rkostrzewski Jun 13, 2017
cbe6052
test: Increase robustness of serve test
rkostrzewski Jun 13, 2017
7a3f7bd
Merge branch 'master' into feature/cli-tests
rkostrzewski Jun 15, 2017
bf1b859
tests: Use chrome-launcher npm package instead of lighthouse
rkostrzewski Jun 15, 2017
1f74d82
tests: Remove cert install
rkostrzewski Jun 15, 2017
ecb762e
tests: New way of handling SSL certificates
rkostrzewski Jun 16, 2017
4e0ec76
tests: New way of handling SSL certificates
rkostrzewski Jun 16, 2017
94038b7
Merge branch 'master' into feature/cli-tests
rkostrzewski Jun 20, 2017
22b3641
Merge developit/master
rkostrzewski Jun 22, 2017
2b6cf54
Merge remote-tracking branch 'developit/feature/cli-tests' into featu…
rkostrzewski Jun 22, 2017
c1d3635
fix: Failing tests for webpack SSR
rkostrzewski Jun 22, 2017
778524d
tests: Add test for webpack prerender
rkostrzewski Jun 22, 2017
9e5d587
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 23, 2017
4d0ce1a
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 24, 2017
9205759
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 25, 2017
5072e1b
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 26, 2017
65a93d9
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 27, 2017
0de7bf9
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 28, 2017
8759601
chore: Remove pacakge-lock.json [ci skip]
rkostrzewski Jun 28, 2017
a1d4ed7
feat: transform config
rkostrzewski Jun 28, 2017
5ffc9fc
Merge remote-tracking branch 'developit/master' into feature/webpack-ssr
rkostrzewski Jun 28, 2017
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
4 changes: 2 additions & 2 deletions src/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export default asyncCommand({
await promisify(rimraf)(dest);
}

let stats = await runWebpack(false, config);
let stats = await runWebpack(false, argv, config);
showStats(stats);

if (argv.json) {
await writeJsonStats(stats)
await writeJsonStats(stats);
}
}
});
31 changes: 2 additions & 29 deletions src/lib/prerender.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,14 @@
import { resolve } from 'path';
import createBabelConfig from './babel-config';

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

let entry = resolve(config.cwd, config.src || 'src', 'index.js'),
let entry = resolve(__dirname, './ssr-build', outputFilename),
url = params.url || '/';

require('babel-register')({
babelrc: false,
...createBabelConfig(config, { modules: 'commonjs' })
});

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

// install CSS modules (just to generate correct classNames and support importing CSS & LESS)
require('css-modules-require-hook')({
rootDir: resolve(config.cwd, config.src || 'src'),
generateScopedName: '[local]__[hash:base64:5]',
extensions: ['.less', '.css'],
processorOpts: { parser: require('postcss-less').parse }
});

// strip webpack loaders from import names
let { Module } = require('module');
let oldResolve = Module._resolveFilename;
Module._resolveFilename = function (request, parent, isMain) {
request = request.replace(/^.*\!/g, '');
return oldResolve.call(this, request, parent, isMain);
};

require('./polyfills');

let m = require(entry),
app = m && m.default || m;

Expand All @@ -47,8 +23,5 @@ export default function prerender(config, params) {

let html = renderToString(preact.h(app, { url }));

// restore resolution without loader stripping
Module._resolveFilename = oldResolve;

return html;
}
118 changes: 81 additions & 37 deletions src/lib/run-webpack.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import { resolve } from 'path';
import path from 'path';
import rimraf from 'rimraf';
import fs from 'fs.promised';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import chalk from 'chalk';

export default (watch=false, config, onprogress) => new Promise( (resolve, reject) => {
let compiler = webpack(config);

let done = (err, stats) => {
if (err) reject(err);
else {
// Timeout for plugins that work on `after-emit` event of webpack
setTimeout(()=>{
resolve(stats);
},20);
}
};

export default (watch=false, env, config, onprogress) => {
if (watch) {
return devBuild(config, onprogress);
}
else {
return prodBuild(env, config);
}
};

const devBuild = (config, onprogress) => {
let compiler = webpack(config);
return new Promise((resolve, reject) => {
let first = true;
compiler.plugin('done', stats => {
if (first) {
first = false;
let devServer = config.devServer
let devServer = config.devServer;
let serverAddr = `${devServer.https===true?'https':'http'}://${process.env.HOST || devServer.host || 'localhost'}:${process.env.PORT || devServer.port || 8080}`;
process.stdout.write(` \u001b[32m> Development server started at ${serverAddr}\u001b[39m\n`);
}
Expand All @@ -32,11 +31,56 @@ export default (watch=false, config, onprogress) => new Promise( (resolve, rejec

let server = new WebpackDevServer(compiler, config.devServer);
server.listen(config.devServer.port);
}
else {
compiler.run(done);
}
});
});
};

const prodBuild = (env, config) => {
let ssrOutputPath = path.resolve(__dirname, 'ssr-build/');
let ssrConfig = (config) => {
let newConfig = Object.assign({}, config, {
target: 'node',
entry: path.resolve(env.cwd, env.src || 'src', 'index.js'),
plugins: config.plugins.filter(c => !(c.constructor && c.constructor.name === 'HtmlWebpackPlugin')),
});

newConfig.output.path = ssrOutputPath;
newConfig.output.libraryTarget = 'commonjs2';

let asyncLoaderIndex = newConfig.module.loaders
.findIndex(l => l.loader === path.resolve(__dirname, 'async-component-loader'));
Copy link
Member

Choose a reason for hiding this comment

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

might be able to just alias this to a dummy / passthrough loader

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah it would be better + whenever someone uses import Component from 'async!./components/...' currently it won't work


// Remove async loader in prerender because all chunks are available.
if (asyncLoaderIndex > -1) {
newConfig.module.loaders.splice(asyncLoaderIndex, 1);
}

return newConfig;
};
let compiler = webpack(config);
let runCompiler = () => new Promise((resolve, reject) => {
console.log('Running production build...');
compiler.run((err, stats) => {
if (err) reject(err);
else {
// Timeout for plugins that work on `after-emit` event of webpack
setTimeout(()=> resolve(stats), 20);
}
});
});

let ssrCompiler = webpack(ssrConfig(config));
let runSsrCompiler = () => new Promise((resolve, reject) => {
console.log('Running SSR build...');
rimraf.sync(ssrOutputPath);
ssrCompiler.run((err) => {
if (err) return reject(err);
// Timeout for plugins that work on `after-emit` event of webpack
setTimeout(()=> resolve(), 20);
});
});

return env.prerender ? runSsrCompiler().then(runCompiler) : runCompiler();
};

export function showStats(stats) {
let info = stats.toJson();
Expand All @@ -57,39 +101,39 @@ export function showStats(stats) {
}

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

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

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')
})
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 normalizeModule = m => {
const keysToNormalize = ['identifier', 'name', 'module', 'moduleName', 'moduleIdentifier']
const keysToNormalize = ['identifier', 'name', 'module', 'moduleName', 'moduleIdentifier'];

keysToNormalize.forEach(key => {
if(key in m) {
m[key] = normalizeName(m[key])
if (key in m) {
m[key] = normalizeName(m[key]);
}
})
});

if (m.reasons) {
m.reasons.forEach(normalizeModule)
m.reasons.forEach(normalizeModule);
}

return m
}
return m;
};

const normalizeName = p => p.substr(p.lastIndexOf('!') + 1)
const normalizeName = p => p.substr(p.lastIndexOf('!') + 1);
5 changes: 3 additions & 2 deletions src/lib/webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ export default env => {

env.pkg = readJson(resolve(cwd, 'package.json')) || {};
env.manifest = readJson(src('manifest.json')) || {};
env.outputFilename = 'bundle.js';

return createConfig.vanilla([
setContext(src('.')),
entryPoint(resolve(__dirname, './entry')),
setOutput({
path: resolve(cwd, env.dest || 'build'),
publicPath: '/',
filename: 'bundle.js',
filename: env.outputFilename,
chunkFilename: '[name].chunk.[chunkhash:5].js'
}),

Expand Down Expand Up @@ -455,7 +456,7 @@ const htmlPlugin = config => addPlugins([
title: config.title || config.manifest.name || config.manifest.short_name || (config.pkg.name || '').replace(/^@[a-z]\//, '') || 'Preact App',
config,
ssr(params) {
return config.prerender ? prerender(config, params) : '';
return config.prerender ? prerender(config.outputFilename, params) : '';
}
}),

Expand Down