[CORL-127] Custom CSS (#2194)

* feat: moved html-webpack-plugin to custom server templates in production

* fix: fixed templates

* fix: removed sri for the time being

* fix: fixed up tests for new field name
This commit is contained in:
Wyatt Johnson
2019-02-13 20:45:11 +00:00
committed by GitHub
parent c91a0fafa5
commit aa2346b715
20 changed files with 492 additions and 392 deletions
+78 -112
View File
@@ -1,7 +1,7 @@
import OptimizeCssnanoPlugin from "@intervolga/optimize-cssnano-plugin";
import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
import CompressionPlugin from "compression-webpack-plugin";
import HtmlWebpackPlugin, { Options } from "html-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { identity } from "lodash";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import path from "path";
@@ -9,14 +9,12 @@ import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModul
import TerserPlugin from "terser-webpack-plugin";
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import webpack, { Configuration, Plugin } from "webpack";
import WebpackAssetsManifest from "webpack-assets-manifest";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import ManifestPlugin from "webpack-manifest-plugin";
import { Config } from "./config";
import { createClientEnv } from "./config";
import paths from "./paths";
import InterpolateHtmlPlugin from "./plugins/InterpolateHtmlPlugin";
import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin";
/**
* filterPlugins will filter out null values from the array of plugins, allowing
@@ -59,23 +57,16 @@ export default function createWebpackConfig(
* ifProduction will only include the nodes if we're in production mode.
*/
const ifProduction = isProduction
? <T extends {}>(...nodes: T[]) => nodes
: <T extends {}>(...nodes: T[]) => [];
? (...nodes: any[]) => nodes
: (...nodes: any[]) => [];
const htmlWebpackConfig: Options = {
minify: minimize && {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
};
/**
* ifNotProduction will only include the nodes if we're not in production
* mode.
*/
const ifNotProduction = !isProduction
? (...nodes: any[]) => nodes
: (...nodes: any[]) => [];
const styleLoader = {
loader: require.resolve("style-loader"),
@@ -532,66 +523,50 @@ export default function createWebpackConfig(
},
plugins: filterPlugins([
...baseConfig.plugins!,
// Generates an `stream.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "stream.html",
template: paths.appStreamHTML,
chunks: ["stream"],
inject: "body",
...htmlWebpackConfig,
}),
// Generates an `auth.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "auth.html",
template: paths.appAuthHTML,
chunks: ["auth"],
inject: "body",
...htmlWebpackConfig,
}),
// Generates an `auth-callback.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "auth-callback.html",
template: paths.appAuthCallbackHTML,
chunks: ["authCallback"],
inject: "body",
...htmlWebpackConfig,
}),
// Generates an `install.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "install.html",
template: paths.appInstallHTML,
chunks: ["install"],
inject: "body",
...htmlWebpackConfig,
}),
// Generates an `admin.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "admin.html",
template: paths.appAdminHTML,
chunks: ["admin"],
inject: "body",
...htmlWebpackConfig,
}),
...ifNotProduction(
// Generates an `stream.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "stream.html",
template: paths.appStreamHTML,
chunks: ["stream"],
inject: "body",
}),
// Generates an `auth.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "auth.html",
template: paths.appAuthHTML,
chunks: ["auth"],
inject: "body",
}),
// Generates an `auth-callback.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "auth-callback.html",
template: paths.appAuthCallbackHTML,
chunks: ["authCallback"],
inject: "body",
}),
// Generates an `install.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "install.html",
template: paths.appInstallHTML,
chunks: ["install"],
inject: "body",
}),
// Generates an `admin.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "admin.html",
template: paths.appAdminHTML,
chunks: ["admin"],
inject: "body",
})
),
...ifProduction(
// Inject the pieces we need here to resolve all the now relative url's
// against the CDN if it's provided. It will inject the following into
// the configuration blob on the page.
new PublicURIWebpackPlugin(
"{{ staticURI | dump | safe }}",
"{{ staticURI }}"
)
new WebpackAssetsManifest({
output: "asset-manifest.json",
entrypoints: true,
integrity: true,
})
),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: "asset-manifest.json",
}),
]),
},
/* Webpack config for our embed */
@@ -622,40 +597,31 @@ export default function createWebpackConfig(
},
plugins: filterPlugins([
...baseConfig.plugins!,
...(isProduction
? []
: [
// Generates an `embed.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "embed.html",
template: paths.appEmbedHTML,
inject: "head",
...htmlWebpackConfig,
}),
new HtmlWebpackPlugin({
filename: "story.html",
template: paths.appEmbedStoryHTML,
inject: "head",
...htmlWebpackConfig,
}),
new HtmlWebpackPlugin({
filename: "storyButton.html",
template: paths.appEmbedStoryButtonHTML,
inject: "head",
...htmlWebpackConfig,
}),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env),
]),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: "embed-manifest.json",
}),
...ifNotProduction(
// Generates an `embed.html` file with the <script> injected.
new HtmlWebpackPlugin({
filename: "embed.html",
template: paths.appEmbedHTML,
inject: "head",
}),
new HtmlWebpackPlugin({
filename: "story.html",
template: paths.appEmbedStoryHTML,
inject: "head",
}),
new HtmlWebpackPlugin({
filename: "storyButton.html",
template: paths.appEmbedStoryButtonHTML,
inject: "head",
})
),
...ifProduction(
new WebpackAssetsManifest({
output: "embed-asset-manifest.json",
entrypoints: true,
integrity: true,
})
),
]),
},
];
@@ -1,45 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// This Webpack plugin lets us interpolate custom variables into `index.html`.
// Usage: `new InterpolateHtmlPlugin({ 'MY_VARIABLE': 42 })`
// Then, you can use %MY_VARIABLE% in your `index.html`.
// It works in tandem with HtmlWebpackPlugin.
// Learn more about creating plugins like this:
// https://github.com/ampedandwired/html-webpack-plugin#events
import escapeStringRegexp from "escape-string-regexp";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { Compiler, Plugin } from "webpack";
export default class InterpolateHtmlPlugin implements Plugin {
private replacements: Record<string, string>;
constructor(replacements: Record<string, string>) {
this.replacements = replacements;
}
public apply(compiler: Compiler) {
// The following was modified from the original source:
// https://github.com/jussikinnula/react-dev-utils/blob/9c290281877c026774c909b2900000c653f431a4/InterpolateHtmlPlugin.js
// to support Webpack 4.
compiler.hooks.compilation.tap("InterpolateHtmlPlugin", compilation => {
const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
hooks.afterTemplateExecution.tap("InterpolateHtmlPlugin", (data: any) => {
// Run HTML through a series of user-specified string replacements.
Object.keys(this.replacements).forEach(key => {
const value = this.replacements[key];
data.html = data.html.replace(
new RegExp("%" + escapeStringRegexp(key) + "%", "g"),
value
);
});
});
});
}
}
@@ -1,93 +0,0 @@
import HtmlWebpackPlugin from "html-webpack-plugin";
import { AsyncSeriesWaterfallHook } from "tapable";
import { Compiler, Plugin } from "webpack";
// Copied from @types/html-webpack-plugin
interface HtmlTagObject {
/**
* Attributes of the html tag
* E.g. `{'disabled': true, 'value': 'demo'}`
*/
attributes: {
[attributeName: string]: string | boolean;
};
/**
* Wether this html must not contain innerHTML
* @see https://www.w3.org/TR/html5/syntax.html#void-elements
*/
voidTag: boolean;
/**
* The tag name e.g. `'div'`
*/
tagName: string;
/**
* Inner HTML The
*/
innerHTML?: string;
}
type AlterAssetTagGroupsHook = AsyncSeriesWaterfallHook<{
headTags: Array<HtmlTagObject | HtmlTagObject>;
bodyTags: Array<HtmlTagObject | HtmlTagObject>;
outputName: string;
plugin: HtmlWebpackPlugin;
}>;
export default class PublicURIWebpackPlugin implements Plugin {
private configTemplate: string;
private prefixTemplate: string;
constructor(configTemplate: string, prefixTemplate: string) {
this.configTemplate = configTemplate;
this.prefixTemplate = prefixTemplate;
}
private prefixAttribute(attr: string | boolean) {
if (!attr || typeof attr !== "string" || !attr.startsWith("/")) {
return attr;
}
return this.prefixTemplate + attr;
}
private prefixTag = (tag: {
tagName: string;
attributes: Record<string, string | boolean>;
}) => {
switch (tag.tagName) {
case "link":
tag.attributes.href = this.prefixAttribute(tag.attributes.href);
break;
case "script":
tag.attributes.src = this.prefixAttribute(tag.attributes.src);
break;
}
};
public apply = (compiler: Compiler) => {
compiler.hooks.compilation.tap("PublicURIWebpackPlugin", compilation => {
const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
(hooks.alterAssetTagGroups as AlterAssetTagGroupsHook).tapAsync(
"PublicURIWebpackPlugin",
(htmlPluginData, cb) => {
// Prefix all the asset's url's with the template.
htmlPluginData.headTags.forEach(this.prefixTag);
htmlPluginData.bodyTags.forEach(this.prefixTag);
// Insert the public path reference.
htmlPluginData.bodyTags.unshift({
tagName: "script",
attributes: {
type: "application/json",
id: "config",
},
innerHTML: this.configTemplate,
voidTag: false,
});
return cb(null, htmlPluginData);
}
);
});
};
}