Skip to content

Instantly share code, notes, and snippets.

@kevinguard
Created November 27, 2017 21:42
Show Gist options
  • Save kevinguard/8d702ddd3188fb667faedd83ab9d8205 to your computer and use it in GitHub Desktop.
Save kevinguard/8d702ddd3188fb667faedd83ab9d8205 to your computer and use it in GitHub Desktop.
Webpack + React + Loading CSS of external modules
const fs = require("fs");
const externalModules = fs.readdirSync("node_modules")
.filter(x => [".bin"].indexOf(x) === -1)
.reduce((acc, cur) => Object.assign(acc, { [cur]: "commonjs " + cur }), {});
module.exports = externalModules;
const path = require("path");
/*
* __dirname is changed after webpack-ed to another directory
* so process.cwd() is used instead to determine the correct base directory
* Read more: https://nodejs.org/api/process.html#process_process_cwd
*/
const CURRENT_WORKING_DIR = process.cwd();
module.exports = {
app: path.resolve(CURRENT_WORKING_DIR, "app"),
assets: path.resolve(CURRENT_WORKING_DIR, "public", "assets"),
compiled: path.resolve(CURRENT_WORKING_DIR, "compiled"),
public: "/assets/", // use absolute path for css-loader?
modules: path.resolve(CURRENT_WORKING_DIR, "node_modules"),
};
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
module.exports = ({ production = false, browser = false } = {}) => {
const bannerOptions = {raw: true, banner: 'require("source-map-support").install();'};
const compress = {warnings: false};
if (!production && !browser) {
return [
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new webpack.DefinePlugin({
__PRODUCTION__: JSON.stringify(production),
__DEVCLIENT__ : false,
__DEVSERVER__ : true,
}),
new webpack.BannerPlugin(bannerOptions),
];
}
if (!production && browser) {
return [
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new webpack.DefinePlugin({
__PRODUCTION__: JSON.stringify(production),
__DEVCLIENT__ : true,
__DEVSERVER__ : false,
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
];
}
if (production && !browser) {
return [
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new webpack.DefinePlugin({
__PRODUCTION__: JSON.stringify(production),
__DEVCLIENT__ : false,
__DEVSERVER__ : false,
}),
new webpack.BannerPlugin(bannerOptions),
new webpack.optimize.UglifyJsPlugin({compress}),
];
}
if (production && browser) {
return [
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new webpack.DefinePlugin({
__PRODUCTION__: JSON.stringify(production),
__DEVCLIENT__ : false,
__DEVSERVER__ : false,
}),
new ExtractTextPlugin({
filename: "[contenthash].css",
allChunks: true
}),
new webpack.optimize.UglifyJsPlugin({compress}),
new ManifestPlugin({
fileName: "manifest.json",
}),
];
}
return [];
};
const PATHS = require("./paths");
module.exports = {
modules: [PATHS.app, PATHS.modules],
extensions: [".js", ".jsx", ".css"],
};
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const postcssImport = require("postcss-import");
const postcssCssnext = require("postcss-cssnext");
const postcssReporter = require("postcss-reporter");
const PATHS = require("../paths");
module.exports = ({ production = false, browser = false } = {}) => {
/*
* modules: boolean - Enable/Disable CSS Modules
* importLoaders: number - Number of loaders applied before CSS loader
*
* Read more about css-loader options
* https://webpack.js.org/loaders/css-loader/#options
*
* For server-side rendering we use css-loader/locals as we do not want to
* embed CSS. However, we still require the mappings to insert as className in
* our views.
*
* Referenced from: https://github.com/webpack-contrib/css-loader#css-scope
*
* For pre-rendering with extract-text-webpack-plugin you should use
* css-loader/locals instead of style-loader!css-loader in the pre-rendering bundle.
* It doesn't embed CSS but only exports the identifier mappings.
*/
const localIndentName = "localIdentName=[name]__[local]___[hash:base64:5]";
const createCssLoaders = embedCssInBundle => ([
{
loader: embedCssInBundle ? "css-loader" : "css-loader/locals",
options: {
localIndentName,
sourceMap: true,
modules: true,
importLoaders: 1,
},
},
{
loader: "postcss-loader",
options: {
plugins: [
postcssImport({path: path.resolve(PATHS.app, "./css")}),
postcssCssnext({browsers: ["> 1%", "last 2 versions"]}),
postcssReporter({clearMessages: true}),
],
},
},
]);
const createBrowserLoaders = extractCssToFile => loaders => {
if (extractCssToFile) {
return ExtractTextPlugin.extract({
fallback: "style-loader",
use: loaders,
});
}
return [
{
loader: "style-loader",
},
...loaders,
];
};
const serverLoaders = createCssLoaders(false);
const browserLoaders = createBrowserLoaders(production)(createCssLoaders(true));
return {
test: /\.css$/,
use: browser ? browserLoaders : serverLoaders,
include: PATHS.app,
};
};
const javascript = require("./javascript");
const css = require("./css");
module.exports = ({production = false, browser = false} = {}) => (
[
javascript({ production, browser }),
css({ production, browser }),
]
);
const PATHS = require("../paths");
module.exports = ({production = false, browser = false} = {}) => {
const enableHotModuleReplacement = !production && browser;
const createPresets = enableHotModuleReplacement => {
const presets = ["env", "react", "stage-0"];
return enableHotModuleReplacement ? ["react-hmre", ...presets] : presets;
};
const presets = createPresets(enableHotModuleReplacement);
const plugins = production ? [
"transform-react-remove-prop-types",
"transform-react-constant-elements",
"transform-react-inline-elements",
] : [];
return {
test: /\.js$|\.jsx$/,
loader: "babel-loader",
options: {
presets,
plugins,
},
exclude: PATHS.modules,
};
};
/*
* process.env.NODE_ENV - used to determine whether we generate a production or development bundle
*
* webpack --env.browser - used to determine whether to generate a browser or server bundle
*
* NOTE: browser/server is client/server-side rendering respectively in universal/isomorphic javascript
*/
const PATHS = require("./paths");
const rules = require("./rules");
const plugins = require("./plugins");
const externals = require("./externals");
const resolve = require("./resolve");
module.exports = (env = {}) => {
const isProduction = process.env.NODE_ENV === "production";
const isBrowser = env.browser;
console.log(`Running webpack in ${process.env.NODE_ENV} mode on ${isBrowser ? "browser" : "server"}`);
const hotMiddlewareScript = "webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true";
const node = {
__dirname: true,
__filename: true
};
const prodServerRender = {
devtool: "source-map",
context: PATHS.app,
entry: { server: "../server/index" },
target: "node",
node,
externals,
output: {
path: PATHS.compiled,
filename: "[name].js",
publicPath: PATHS.public,
libraryTarget: "commonjs2"
},
module: { rules: rules({ production: true, browser: false }) },
resolve,
plugins: plugins({ production: true, browser: false })
};
const prodBrowserRender = {
devtool: "cheap-module-source-map",
context: PATHS.app,
entry: { app: ["./client"] },
node,
output: {
path: PATHS.assets,
filename: "[chunkhash].js",
chunkFilename: "[name].[chunkhash:6].js", // for code splitting. will work without but useful to set
publicPath: PATHS.public
},
module: { rules: rules({ production: true, browser: true }) },
resolve,
plugins: plugins({ production: true, browser: true })
};
const devBrowserRender = {
devtool: "eval",
context: PATHS.app,
entry: { app: ["./client", hotMiddlewareScript] },
node,
output: {
path: PATHS.assets,
filename: "[name].js",
publicPath: PATHS.public
},
module: { rules: rules({ production: false, browser: true }) },
resolve,
plugins: plugins({ production: false, browser: true }),
};
const devServerRender = {
devtool: "sourcemap",
context: PATHS.app,
entry: { server: "../server/index" },
target: "node",
node,
externals,
output: {
path: PATHS.compiled,
filename: "[name].dev.js",
publicPath: PATHS.public,
libraryTarget: "commonjs2",
},
module: { rules: rules({ production: false, browser: false }) },
resolve,
plugins: plugins({ production: false, browser: false }),
};
const prodConfig = isBrowser ? prodBrowserRender : prodServerRender;
const devConfig = isBrowser ? devBrowserRender : devServerRender;
return isProduction ? prodConfig : devConfig;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment