Skip to content

Instantly share code, notes, and snippets.

@kastermester
Created October 25, 2016 11:51
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kastermester/80f73b6a0687f4190ac739b78e8c4395 to your computer and use it in GitHub Desktop.
Save kastermester/80f73b6a0687f4190ac739b78e8c4395 to your computer and use it in GitHub Desktop.
Webpack hot reloading of relay schemas
// loaction: loaders/es-preset.js
// This file is a combination of the following babel presets:
// es2015, react, stage-0.
// Difference is that the es2015-template-literals is taken out. Of the es2015 preset.
// This allows us to run relay transformations on the code afterwards (which will then transform any remaining template literals).
module.exports = preset({});
function preset(context, opts) {
var moduleTypes = ["commonjs", "amd", "umd", "systemjs"];
var loose = false;
var modules = "commonjs";
var spec = false;
if (opts !== undefined) {
if (opts.loose !== undefined) loose = opts.loose;
if (opts.modules !== undefined) modules = opts.modules;
if (opts.spec !== undefined) spec = opts.spec;
}
if (typeof loose !== "boolean") throw new Error("Preset es2015 'loose' option must be a boolean.");
if (typeof spec !== "boolean") throw new Error("Preset es2015 'spec' option must be a boolean.");
if (modules !== false && moduleTypes.indexOf(modules) === -1) {
throw new Error("Preset es2015 'modules' option must be 'false' to indicate no modules\n" +
"or a module type which be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'");
}
return {
plugins: [
require("babel-plugin-transform-es2015-literals"),
require("babel-plugin-transform-es2015-function-name"),
[require("babel-plugin-transform-es2015-arrow-functions"), { spec: spec }],
require("babel-plugin-transform-es2015-block-scoped-functions"),
[require("babel-plugin-transform-es2015-classes"), { loose: loose }],
require("babel-plugin-transform-es2015-object-super"),
require("babel-plugin-transform-es2015-shorthand-properties"),
require("babel-plugin-transform-es2015-duplicate-keys"),
[require("babel-plugin-transform-es2015-computed-properties"), { loose: loose }],
[require("babel-plugin-transform-es2015-for-of"), { loose: loose }],
require("babel-plugin-transform-es2015-sticky-regex"),
require("babel-plugin-transform-es2015-unicode-regex"),
require("babel-plugin-check-es2015-constants"),
[require("babel-plugin-transform-es2015-spread"), { loose: loose }],
require("babel-plugin-transform-es2015-parameters"),
[require("babel-plugin-transform-es2015-destructuring"), { loose: loose }],
require("babel-plugin-transform-es2015-block-scoping"),
require("babel-plugin-transform-es2015-typeof-symbol"),
modules === "commonjs" && [require("babel-plugin-transform-es2015-modules-commonjs"), { loose: loose }],
modules === "systemjs" && [require("babel-plugin-transform-es2015-modules-systemjs"), { loose: loose }],
modules === "amd" && [require("babel-plugin-transform-es2015-modules-amd"), { loose: loose }],
modules === "umd" && [require("babel-plugin-transform-es2015-modules-umd"), { loose: loose }],
[require("babel-plugin-transform-regenerator"), { async: false, asyncGenerators: false }],
// react
require("babel-plugin-transform-react-jsx"),
require("babel-plugin-transform-flow-strip-types"),
require("babel-plugin-syntax-flow"),
require("babel-plugin-syntax-jsx"),
// stage-3
require("babel-plugin-syntax-trailing-function-commas"),
require("babel-plugin-transform-async-to-generator"),
require("babel-plugin-transform-exponentiation-operator"),
// stage-2
require("babel-plugin-transform-class-properties"),
require("babel-plugin-transform-object-rest-spread"),
require("babel-plugin-transform-decorators"),
// stage-1
require("babel-plugin-transform-class-constructor-call"),
require("babel-plugin-transform-export-extensions"),
// stage-0
require("babel-plugin-transform-react-display-name"),
require("babel-plugin-syntax-trailing-function-commas"),
require("babel-plugin-transform-async-to-generator"),
require("babel-plugin-transform-exponentiation-operator"),
// filter out falsy values
].filter(Boolean)
};
}
// location: loaders/relay-loader.js
var path = require('path');
var fs = require('fs');
var loaderUtils = require('loader-utils');
var babel = require('babel-core');
var babelRelayPluginConstructor = require('babel-relay-plugin');
// Some of this code is shamelessly stolen from babel-loader
/**
* Error thrown by Babel formatted to conform to Webpack reporting.
*/
function BabelLoaderError(name, message, codeFrame, hideStack, error) {
Error.call(this);
Error.captureStackTrace(this, BabelLoaderError);
this.name = 'BabelLoaderError';
this.message = formatMessage(name, message, codeFrame);
this.hideStack = hideStack;
this.error = error;
}
BabelLoaderError.prototype = Object.create(Error.prototype);
BabelLoaderError.prototype.constructor = BabelLoaderError;
var STRIP_FILENAME_RE = /^[^:]+: /;
var formatMessage = function(name, message, codeFrame) {
return (name ? name + ': ' : '') + message + '\n\n' + codeFrame + '\n';
};
function transpile(source, options, callback) {
var result;
try {
result = babel.transform(source, options);
} catch (error) {
if (error.message && error.codeFrame) {
var message = error.message;
var name;
var hideStack;
if (error instanceof SyntaxError) {
message = message.replace(STRIP_FILENAME_RE, '');
name = 'SyntaxError';
hideStack = true;
} else if (error instanceof TypeError) {
message = message.replace(STRIP_FILENAME_RE, '');
hideStack = true;
}
return callback(
new BabelLoaderError(
name,
message,
error.codeFrame,
hideStack,
error
)
);
} else {
callback(error);
}
}
var code = result.code;
var map = result.map;
if (map && (!map.sourcesContent || !map.sourcesContent.length)) {
map.sourcesContent = [source];
}
callback(null, code, map);
}
module.exports = function(source, inputSourceMap) {
this.cacheable();
var schemaFile = (this.options.relay && this.options.relay.schemaFile);
var callback = this.async();
if (!schemaFile) {
return callback(new Error("Specify schema file in webpack config at relay.schemaFile. Must be absolute path"));
}
var webpackRemainingChain = loaderUtils.getRemainingRequest(this).split('!');
var filename = webpackRemainingChain[webpackRemainingChain.length - 1];
// I disable the ts-loaders behavior of depending on every single file this file actually depends on.
// To be completely 100% accurate this is what is needed. However it also slows down the entire system
// by quite a bit. (Isolated simple example cut a few seconds off hot-reload times, which I consider significant).
// This means if you change exports in a file (like remove a default export) other files won't be recompiled because
// of it. This might prove to be a bad idea, luckily it is easily removed.
this.clearDependencies();
this.addDependency(filename);
if (this.options.relay && this.options.relay.omitFiles && this.options.relay.omitFiles.indexOf(filename) >= 0) {
return callback(null, source, inputSourceMap);
}
this.addDependency(schemaFile);
fs.readFile(schemaFile, 'utf-8', function(err, result) {
if (err) {
return callback(err);
}
var json;
try {
json = JSON.parse(result);
} catch (e) {
return callback(err);
}
var plugin = babelRelayPluginConstructor(json.data);
var babelOpts = {
plugins: [plugin, require('babel-plugin-transform-es2015-template-literals')],
filename: filename,
inputSourceMap: inputSourceMap,
sourceRoot: process.cwd(),
env: process.env.BABEL_ENV || process.env.NODE_ENV,
sourceMap: true,
};
transpile(source, babelOpts, callback);
});
}
var webpack = require('webpack');
var path = require('path');
var plugins = [];
var entries = [path.resolve(__dirname, 'site/index.tsx')];
var env = 'development';
if (process.env.NODE_ENV === 'development' || typeof(process.env.NODE_ENV) === 'undefined') {
plugins.push(new webpack.HotModuleReplacementPlugin());
entries.unshift('webpack-hot-middleware/client');
}
if (process.env.NODE_ENV === 'production') {
plugins.push(new webpack.NoErrorsPlugin());
env = 'production';
}
var relayLoader = path.resolve(__dirname, 'loaders', 'relay-loader');
module.exports = {
entry: entries,
context: path.resolve(__dirname),
output: {
path: path.resolve(__dirname, 'build', 'assets'),
filename: 'app.js',
pathinfo: env === 'deveploment',
publicPath: '/assets/',
},
resolve: {
extensions: ['', '.js', '.ts', '.tsx', '.json'],
},
debug: true,
target: 'web',
devtool: env === 'development' ? 'cheap-module-source-map' : 'source-map',
plugins: plugins,
module: {
loaders: [
{
test: /\.ts(x?)$/,
loader: relayLoader + '!babel-loader!ts-loader',
exclude: /\/node_modules\//,
}
]
},
HMR: env === 'development',
babel: {
"passPerPreset": false,
"sourceMap": true,
"presets": [
{
"plugins": [
[
'transform-runtime',
{
helpers: true,
polyfill: true,
regenerator: true,
},
],
],
},
require('./loaders/preset-es'),
],
"env": {
"production": {
"presets": ["react-optimize"]
},
// only enable it when process.env.NODE_ENV is 'development' or undefined
"development": {
"plugins": [
[
"react-transform",
{
"transforms": [
{
"transform": "react-transform-hmr",
// if you use React Native, pass "react-native" instead:
"imports": ["react"],
// this is important for Webpack HMR:
"locals": ["module"]
}
]
},
{
// you can have many transforms, not just one
"transform": "react-transform-catch-errors",
"imports": ["react", "redbox-react"]
}
]
]
}
}
},
relay: {
schemaFile: path.resolve(__dirname, 'data', 'schema.json'),
},
ts: {
silent: true,
transpileOnly: true,
configFileName: path.resolve(__dirname, 'tsconfig.json'),
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment